Commit a036278c authored by Kalvin Lee's avatar Kalvin Lee Committed by Commit Bot

printing: implement PrinterConfigCache

This change implements the PrinterConfigCache and provides unit tests
for the same.

Bug: 888189
Change-Id: I01ca78b196fdc3392464f7c033bfa2b2db4b2a00
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2013321
Commit-Queue: Kalvin Lee <kdlee@chromium.org>
Reviewed-by: default avatarSean Kau <skau@chromium.org>
Reviewed-by: default avatarLuum Habtemariam <luum@chromium.org>
Cr-Commit-Position: refs/heads/master@{#746892}
parent 0a3d2ad4
...@@ -91,7 +91,11 @@ component("chromeos") { ...@@ -91,7 +91,11 @@ component("chromeos") {
} }
if (is_printing_ppd_provider_v3) { if (is_printing_ppd_provider_v3) {
sources += [ "printing/ppd_provider_v3.cc" ] sources += [
"printing/ppd_provider_v3.cc",
"printing/printer_config_cache.cc",
"printing/printer_config_cache.h",
]
} else { } else {
sources += [ "printing/ppd_provider.cc" ] sources += [ "printing/ppd_provider.cc" ]
} }
...@@ -194,7 +198,6 @@ test("chromeos_unittests") { ...@@ -194,7 +198,6 @@ test("chromeos_unittests") {
"printing/epson_driver_matching_unittest.cc", "printing/epson_driver_matching_unittest.cc",
"printing/ppd_cache_unittest.cc", "printing/ppd_cache_unittest.cc",
"printing/ppd_line_reader_unittest.cc", "printing/ppd_line_reader_unittest.cc",
"printing/ppd_provider_unittest.cc",
"printing/printer_configuration_unittest.cc", "printing/printer_configuration_unittest.cc",
"printing/printer_translator_unittest.cc", "printing/printer_translator_unittest.cc",
"printing/usb_printer_id_unittest.cc", "printing/usb_printer_id_unittest.cc",
...@@ -203,6 +206,12 @@ test("chromeos_unittests") { ...@@ -203,6 +206,12 @@ test("chromeos_unittests") {
"test/run_all_unittests.cc", "test/run_all_unittests.cc",
] ]
if (is_printing_ppd_provider_v3) {
sources += [ "printing/printer_config_cache_unittest.cc" ]
} else {
sources += [ "printing/ppd_provider_unittest.cc" ]
}
data = [ "test/data/" ] data = [ "test/data/" ]
} }
......
...@@ -91,8 +91,8 @@ scoped_refptr<PpdProvider> PpdProvider::Create( ...@@ -91,8 +91,8 @@ scoped_refptr<PpdProvider> PpdProvider::Create(
const PpdProvider::Options& options) { const PpdProvider::Options& options) {
// TODO(crbug.com/888189): use |loader_factory| and do away with // TODO(crbug.com/888189): use |loader_factory| and do away with
// |ppd_cache|. // |ppd_cache|.
return base::MakeRefCounted<PpdProvider>(browser_locale, current_version, return base::MakeRefCounted<PpdProviderImpl>(browser_locale, current_version,
options); options);
} }
} // namespace chromeos } // namespace chromeos
// Copyright 2020 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 "chromeos/printing/printer_config_cache.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/task/post_task.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "url/gurl.h"
namespace chromeos {
namespace {
// Defines the serving root in which all PPDs and PPD metadata reside.
const char kServingRoot[] =
"https://printerconfigurations.googleusercontent.com/"
"chromeos_printing/";
// Prepends the serving root to |name|, returning the result.
std::string PrependServingRoot(const std::string& name) {
return base::StrCat({base::StringPiece(kServingRoot), name});
}
// Accepts a relative |path| to a value in the Chrome OS Printing
// serving root) and returns a resource request to satisfy the same.
std::unique_ptr<network::ResourceRequest> FormRequest(const std::string& path) {
GURL full_url(PrependServingRoot(path));
if (!full_url.is_valid()) {
return nullptr;
}
auto request = std::make_unique<network::ResourceRequest>();
request->url = full_url;
request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
return request;
}
} // namespace
// In case of fetch failure, only the key is meaningful feedback.
// static
PrinterConfigCache::FetchResult PrinterConfigCache::FetchResult::Failure(
const std::string& key) {
return PrinterConfigCache::FetchResult{false, key, std::string(),
base::Time()};
}
// static
PrinterConfigCache::FetchResult PrinterConfigCache::FetchResult::Success(
const std::string& key,
const std::string& contents,
base::Time time_of_fetch) {
return PrinterConfigCache::FetchResult{true, key, contents, time_of_fetch};
}
class PrinterConfigCacheImpl : public PrinterConfigCache {
public:
explicit PrinterConfigCacheImpl(
const base::Clock* clock,
network::mojom::URLLoaderFactory* loader_factory)
: clock_(clock), loader_factory_(loader_factory), weak_factory_(this) {}
~PrinterConfigCacheImpl() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void Fetch(const std::string& key,
base::TimeDelta expiration,
FetchCallback cb) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Try to answer this fetch request locally.
const auto& finding = cache_.find(key);
if (finding != cache_.end()) {
const Entry& entry = finding->second;
if (entry.time_of_fetch + expiration > clock_->Now()) {
std::move(cb).Run(
FetchResult::Success(key, entry.contents, entry.time_of_fetch));
return;
}
}
// We couldn't answer this request locally. Issue a networked fetch
// and defer the answer to when we hear back.
auto context = std::make_unique<FetchContext>(key, std::move(cb));
fetch_queue_.push(std::move(context));
TryToStartNetworkedFetch();
}
void Drop(const std::string& key) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cache_.erase(key);
}
private:
// A FetchContext saves off the key and the FetchCallback that a
// caller passes to PrinterConfigCacheImpl::Fetch().
struct FetchContext {
const std::string key;
PrinterConfigCache::FetchCallback cb;
FetchContext(const std::string& arg_key,
PrinterConfigCache::FetchCallback arg_cb)
: key(arg_key), cb(std::move(arg_cb)) {}
~FetchContext() = default;
};
// If a PrinterConfigCache maps keys to values, then Entry structs
// represent values.
struct Entry {
std::string contents;
base::Time time_of_fetch;
Entry(const std::string& arg_contents, base::Time time)
: contents(arg_contents), time_of_fetch(time) {}
~Entry() = default;
};
void TryToStartNetworkedFetch() {
// Either
// 1. a networked fetch is already in flight or
// 2. there are no more pending networked fetches to act upon.
// In either case, we can't do anything at the moment; back off
// and let a future call to Fetch() or FinishNetworkedFetch()
// return here to try again.
if (fetcher_ || fetch_queue_.empty()) {
return;
}
std::unique_ptr<FetchContext> context = std::move(fetch_queue_.front());
fetch_queue_.pop();
auto request = FormRequest(context->key);
// TODO(crbug.com/888189): add traffic annotation.
fetcher_ = network::SimpleURLLoader::Create(std::move(request),
MISSING_TRAFFIC_ANNOTATION);
fetcher_->DownloadToString(
loader_factory_,
base::BindOnce(&PrinterConfigCacheImpl::FinishNetworkedFetch,
weak_factory_.GetWeakPtr(), std::move(context)),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
}
// Called by |fetcher_| once DownloadToString() completes.
void FinishNetworkedFetch(std::unique_ptr<FetchContext> context,
std::unique_ptr<std::string> contents) {
// Wherever |fetcher_| works its sorcery, it had better have posted
// back onto _our_ sequence.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (fetcher_->NetError() == net::Error::OK) {
// We only want to update our local cache if the |fetcher_|
// succeeded; otherwise, prefer to either retain the stale entry
// (if extant) or retain no entry at all (if not).
const Entry newly_inserted = Entry(*contents, clock_->Now());
cache_.insert_or_assign(context->key, newly_inserted);
std::move(context->cb)
.Run(FetchResult::Success(context->key, newly_inserted.contents,
newly_inserted.time_of_fetch));
} else {
std::move(context->cb).Run(FetchResult::Failure(context->key));
}
fetcher_.reset();
TryToStartNetworkedFetch();
}
// The heart of an PrinterConfigCache: the local cache itself.
base::flat_map<std::string, Entry> cache_;
// Enqueues networked requests.
base::queue<std::unique_ptr<FetchContext>> fetch_queue_;
// Dispenses Time objects to mark time of fetch on Entry instances.
const base::Clock* clock_;
// Mutably borrowed from caller at construct-time.
network::mojom::URLLoaderFactory* loader_factory_;
// Talks to the networked service to fetch resources.
//
// Because this class is sequenced, a non-nullptr value here (observed
// on-sequence) denotes an ongoing fetch. See the
// TryToStartNetworkedFetch() and FinishNetworkedFetch() methods.
std::unique_ptr<network::SimpleURLLoader> fetcher_;
SEQUENCE_CHECKER(sequence_checker_);
// Dispenses weak pointers to our |fetcher_|. This is necessary
// because |this| could be deleted while the loader is in flight
// off-sequence.
base::WeakPtrFactory<PrinterConfigCacheImpl> weak_factory_;
};
// static
std::unique_ptr<PrinterConfigCache> PrinterConfigCache::Create(
const base::Clock* clock,
network::mojom::URLLoaderFactory* loader_factory) {
return std::make_unique<PrinterConfigCacheImpl>(clock, loader_factory);
}
} // namespace chromeos
// Copyright 2020 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.
//
// The PrinterConfigCache class accepts requests to fetch things from
// the Chrome OS Printing serving root. It only stores things in memory.
//
// In practice, the present class fetches either PPDs or PPD metadata.
#ifndef CHROMEOS_PRINTING_PRINTER_CONFIG_CACHE_H_
#define CHROMEOS_PRINTING_PRINTER_CONFIG_CACHE_H_
#include <memory>
#include <string>
#include "base/containers/flat_map.h"
#include "base/containers/queue.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_piece.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chromeos/chromeos_export.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
namespace chromeos {
// A PrinterConfigCache maps keys to values. By convention, keys are
// relative paths to files in the Chrome OS Printing serving root
// (hardcoded into this class). In practice, that means keys will either
// * start with "metadata_v3/" or
// * start with "ppds_for_metadata_v3/."
//
// This class must always be constructed on, used on, and destroyed from
// a sequenced context.
//
// TODO(crbug.com/888189): remove CHROMEOS_EXPORT and refactor all this
// into a dedicated gn component.
class CHROMEOS_EXPORT PrinterConfigCache {
public:
static std::unique_ptr<PrinterConfigCache> Create(
const base::Clock* clock,
network::mojom::URLLoaderFactory* loader_factory);
virtual ~PrinterConfigCache() = default;
// Result of calling Fetch(). The |key| identifies how Fetch() was
// originally invoked. The |contents| and |time_of_fetch| are well-
// defined iff |succeeded| is true.
struct FetchResult {
static FetchResult Failure(const std::string& key);
static FetchResult Success(const std::string& key,
const std::string& contents,
base::Time time_of_fetch);
bool succeeded;
std::string key;
std::string contents;
base::Time time_of_fetch;
};
// Caller is responsible for providing sequencing of this type.
using FetchCallback = base::OnceCallback<void(const FetchResult&)>;
// Queries the Chrome OS Printing serving root for |key|. Calls |cb|
// with the contents. If an entry newer than |expiration| is resident,
// calls |cb| immediately with those contents. Caller should not pass
// keys with leading slashes.
//
// Using TimeDelta implies the caller is asking for "some entry not
// older than |expiration|," e.g. "metadata_v3/index-00.json that
// was fetched within the last 30 minutes."
//
// Naturally,
// * passing the Max() TimeDelta means "perform this Fetch() with no
// limit on staleness" and
// * passing a zero TimeDelta should practically force a networked
// fetch (less esoteric timing quirks etc.).
virtual void Fetch(const std::string& key,
base::TimeDelta expiration,
FetchCallback cb) = 0;
// Drops Entry corresponding to |key|.
virtual void Drop(const std::string& key) = 0;
};
} // namespace chromeos
#endif // CHROMEOS_PRINTING_PRINTER_CONFIG_CACHE_H_
This diff is collapsed.
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