blob: fdca5883690e2fa34bb76ff9a941e1ed2bfc4c70 [file] [log] [blame]
// 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/callback.h"
#include "base/containers/queue.h"
#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/threading/sequenced_task_runner_handle.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/resource_request.h"
#include "third_party/abseil-cpp/absl/types/optional.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,
base::RepeatingCallback<network::mojom::URLLoaderFactory*()>
loader_factory_dispenser)
: clock_(clock),
loader_factory_dispenser_(std::move(loader_factory_dispenser)),
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()) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), 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_dispenser_.Run(),
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);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(context->cb),
FetchResult::Success(
context->key, newly_inserted.contents,
newly_inserted.time_of_fetch)));
} else {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(context->cb),
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_;
// Dispenses fresh URLLoaderFactory instances; see header comment
// on Create().
base::RepeatingCallback<network::mojom::URLLoaderFactory*()>
loader_factory_dispenser_;
// 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,
base::RepeatingCallback<network::mojom::URLLoaderFactory*()>
loader_factory_dispenser) {
return std::make_unique<PrinterConfigCacheImpl>(
clock, std::move(loader_factory_dispenser));
}
} // namespace chromeos