Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7556)

Unified Diff: chromeos/printing/ppd_provider.cc

Issue 2613683004: Completely rewrite the PpdProvider/PpdCache to use the SCS backend. Along the way, clean it up a l… (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chromeos/printing/ppd_provider.cc
diff --git a/chromeos/printing/ppd_provider.cc b/chromeos/printing/ppd_provider.cc
index 133f771744cd04f2c8c61f16d525e107686ea515..728daf810f22d117cde736f1b59cfc5dff9f7d1a 100644
--- a/chromeos/printing/ppd_provider.cc
+++ b/chromeos/printing/ppd_provider.cc
@@ -4,8 +4,11 @@
#include "chromeos/printing/ppd_provider.h"
+#include <deque>
+#include <functional>
skau 2017/01/05 20:38:46 Do you need this header? Most of the included fea
Carlson 2017/01/26 21:59:36 Not sure what this got put in for. Removed.
#include <unordered_map>
#include <utility>
+#include <vector>
#include "base/base64.h"
#include "base/bind_helpers.h"
@@ -13,6 +16,7 @@
#include "base/json/json_parser.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
@@ -32,445 +36,616 @@ namespace chromeos {
namespace printing {
namespace {
-// Expected fields from the quirks server.
-const char kJSONPPDKey[] = "compressedPpd";
-const char kJSONLastUpdatedKey[] = "lastUpdatedTime";
-const char kJSONTopListKey[] = "manufacturers";
-const char kJSONManufacturer[] = "manufacturer";
-const char kJSONModelList[] = "models";
-
-class PpdProviderImpl;
-
-// PpdProvider handles two types of URL fetches, and so uses these delegates
-// to route OnURLFetchComplete to the appropriate handler.
-class ResolveURLFetcherDelegate : public net::URLFetcherDelegate {
- public:
- explicit ResolveURLFetcherDelegate(PpdProviderImpl* parent)
- : parent_(parent) {}
-
- void OnURLFetchComplete(const net::URLFetcher* source) override;
+// Returns false if there are obvious errors in the reference that will prevent
+// resolution.
+bool PpdReferenceIsWellFormed(const Printer::PpdReference& reference) {
+ int filled_fields = 0;
+ if (!reference.user_supplied_ppd_url.empty()) {
+ ++filled_fields;
+ if (!GURL(reference.user_supplied_ppd_url).is_valid()) {
+ return false;
+ }
+ }
+ if (!reference.effective_model.empty()) {
+ ++filled_fields;
+ }
+ // Should have exactly one non-empty field.
+ return filled_fields == 1;
+}
- // Link back to parent. Not owned.
- PpdProviderImpl* const parent_;
-};
+std::string 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_model.empty()) {
+ return std::string("em:") + reference.effective_model;
+ } else {
+ return std::string("up:") + reference.user_supplied_ppd_url;
+ }
+}
-class QueryAvailableURLFetcherDelegate : public net::URLFetcherDelegate {
+class PpdProviderImpl : public PpdProvider, public net::URLFetcherDelegate {
public:
- explicit QueryAvailableURLFetcherDelegate(PpdProviderImpl* parent)
- : parent_(parent) {}
-
- void OnURLFetchComplete(const net::URLFetcher* source) override;
-
- // Link back to parent. Not owned.
- PpdProviderImpl* const parent_;
-};
-
-// Data involved in an active Resolve() URL fetch.
-struct ResolveFetchData {
- // The fetcher doing the fetch.
- std::unique_ptr<net::URLFetcher> fetcher;
+ // What kind of thing is the fetcher currently fetching? We use this to
+ // determine what to do when the fetcher returns a result.
+ enum FetcherTarget {
+ FT_LOCALES, // Locales metadata.
+ FT_MANUFACTURERS, // List of manufacturers metadata.
+ FT_PRINTERS, // List of printers from a manufacturer.
+ FT_PPD_INDEX, // Master ppd index.
+ FT_PPD // A Ppd file.
+ };
- // The reference being resolved.
- Printer::PpdReference ppd_reference;
-
- // Callback to invoke on completion.
- PpdProvider::ResolveCallback done_callback;
-};
-
-// Data involved in an active QueryAvailable() URL fetch.
-struct QueryAvailableFetchData {
- // The fetcher doing the fetch.
- std::unique_ptr<net::URLFetcher> fetcher;
-
- // Callback to invoke on completion.
- PpdProvider::QueryAvailableCallback done_callback;
-};
-
-class PpdProviderImpl : public PpdProvider {
- public:
PpdProviderImpl(
- const std::string& api_key,
+ const std::string& browser_locale,
scoped_refptr<net::URLRequestContextGetter> url_context_getter,
- scoped_refptr<base::SequencedTaskRunner> io_task_runner,
- std::unique_ptr<PpdCache> cache,
+ scoped_refptr<PpdCache> ppd_cache,
const PpdProvider::Options& options)
- : api_key_(api_key),
+ : browser_locale_(browser_locale),
url_context_getter_(url_context_getter),
- io_task_runner_(io_task_runner),
- cache_(std::move(cache)),
+ ppd_cache_(ppd_cache),
options_(options),
- resolve_delegate_(this),
- query_available_delegate_(this),
weak_factory_(this) {
CHECK_GT(options_.max_ppd_contents_size_, 0U);
}
- ~PpdProviderImpl() override {}
- void Resolve(const Printer::PpdReference& ppd_reference,
- const PpdProvider::ResolveCallback& cb) override {
- CHECK(!cb.is_null());
+ // Resolving manufacturers requires a couple of steps, because of
+ // localization. First we have to figure out what locale to use, which
+ // involves grabbing a list of available locales from the server. Once we
+ // have decided on a locale, we go out and fetch the manufacturers map in that
+ // localization.
+ //
+ // This means when a request comes in, we either queue it and start background
+ // fetches if necessary, or we satisfy it immediately from memory.
+ void ResolveManufacturers(const ResolveManufacturersCallback& cb) override {
CHECK(base::SequencedTaskRunnerHandle::IsSet())
- << "Resolve must be called from a SequencedTaskRunner context";
- auto cache_result = base::MakeUnique<base::Optional<base::FilePath>>();
- auto* raw_cache_result_ptr = cache_result.get();
- bool post_result = io_task_runner_->PostTaskAndReply(
- FROM_HERE, base::Bind(&PpdProviderImpl::ResolveDoCacheLookup,
- weak_factory_.GetWeakPtr(), ppd_reference,
- raw_cache_result_ptr),
- base::Bind(&PpdProviderImpl::ResolveCacheLookupDone,
- weak_factory_.GetWeakPtr(), ppd_reference, cb,
- base::Passed(&cache_result)));
- DCHECK(post_result);
+ << "ResolveManufacturers must be called from a SequencedTaskRunner"
+ "context";
+ if (manufacturer_map_.get() == nullptr) {
+ manufacturers_resolution_queue_.push_back(cb);
+ MaybeStartFetch();
skau 2017/01/05 20:38:46 Can you make this an early return? Unless your in
Carlson 2017/01/26 21:59:36 Done.
+ } else {
+ // We already have this in memory.
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, *manufacturer_map_));
+ }
}
- void QueryAvailable(const QueryAvailableCallback& cb) override {
- CHECK(!cb.is_null());
- CHECK(base::SequencedTaskRunnerHandle::IsSet())
- << "QueryAvailable() must be called from a SequencedTaskRunner context";
- auto cache_result =
- base::MakeUnique<base::Optional<PpdProvider::AvailablePrintersMap>>();
- auto* raw_cache_result_ptr = cache_result.get();
- bool post_result = io_task_runner_->PostTaskAndReply(
- FROM_HERE, base::Bind(&PpdProviderImpl::QueryAvailableDoCacheLookup,
- weak_factory_.GetWeakPtr(), raw_cache_result_ptr),
- base::Bind(&PpdProviderImpl::QueryAvailableCacheLookupDone,
- weak_factory_.GetWeakPtr(), cb,
- base::Passed(&cache_result)));
- DCHECK(post_result);
+ // If there is work outstanding that requires a URL fetch to complete, start
+ // going on it.
+ void MaybeStartFetch() {
+ if (fetcher_.get() != nullptr) {
+ // Something is already in flight. We'll call this again when that
+ // completes.
+ return;
+ }
skau 2017/01/05 20:38:46 nit: add a new line
Carlson 2017/01/26 21:59:36 Done.
+ bool redo_from_start;
skau 2017/01/05 20:38:46 Can you break instead of using this flag? It'll r
Carlson 2017/01/26 21:59:36 Restructured a bit to reduce the loop complexity.
+ do {
+ redo_from_start = false;
+ if (!manufacturers_resolution_queue_.empty()) {
+ StartFetch(GetLocalesURL(), FT_LOCALES);
+ } else if (!printers_resolution_queue_.empty()) {
+ StartFetch(GetPrintersURL(printers_resolution_queue_.front().first),
+ FT_PRINTERS);
+ } else if (!ppd_resolution_queue_.empty()) {
+ const auto& next = ppd_resolution_queue_.front();
+ if (!next.first.user_supplied_ppd_url.empty()) {
+ DCHECK(next.first.effective_model.empty());
+ GURL url(next.first.user_supplied_ppd_url);
+ DCHECK(url.is_valid());
+ StartFetch(url, FT_PPD);
+ } else {
+ DCHECK(!next.first.effective_model.empty());
+ if (cached_ppd_index_.get() == nullptr) {
+ // Have to have the ppd index before we can resolve by effective
+ // model
+ StartFetch(GetPpdIndexURL(), FT_PPD_INDEX);
+ } else {
+ // Get the URL from the ppd index and start the fetch.
+ auto it = cached_ppd_index_->find(next.first.effective_model);
+ if (it != cached_ppd_index_->end()) {
+ StartFetch(GetPpdURL(it->second), FT_PPD);
+ } else {
+ // This ppd reference isn't in the index. That's not good. Fail
+ // out the current resolution and go try to start the next
+ // thing if there is one.
+ LOG(ERROR) << "PPD " << next.first.effective_model
+ << " not found in server index";
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(next.second, PpdProvider::INTERNAL_ERROR,
+ std::string()));
+ ppd_resolution_queue_.pop_front();
+ redo_from_start = true;
+ }
+ }
+ }
+ }
+ } while (redo_from_start);
}
- bool CachePpd(const Printer::PpdReference& ppd_reference,
- const base::FilePath& ppd_path) override {
- std::string buf;
- if (!base::ReadFileToStringWithMaxSize(ppd_path, &buf,
- options_.max_ppd_contents_size_)) {
- return false;
+ void ResolvePrinters(const ManufacturerReference& manufacturer,
+ const ResolvePrintersCallback& cb) override {
+ auto it = cached_printers_.find(manufacturer.key);
+ if (it != cached_printers_.end()) {
+ // Satisfy from the cache.
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, it->second));
+ } else {
+ // We haven't resolved this manufacturer yet.
+ printers_resolution_queue_.push_back({manufacturer, cb});
+ MaybeStartFetch();
}
- return static_cast<bool>(cache_->Store(ppd_reference, buf));
}
- // Called on the same thread as Resolve() when the fetcher completes its
- // fetch.
- void OnResolveFetchComplete(const net::URLFetcher* source) {
- std::unique_ptr<ResolveFetchData> fetch_data = GetResolveFetchData(source);
- std::string ppd_contents;
- uint64_t last_updated_time;
- if (!ExtractResolveResponseData(source, fetch_data.get(), &ppd_contents,
- &last_updated_time)) {
- fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR,
- base::FilePath());
+ void ResolvePpd(const Printer::PpdReference& reference,
+ const ResolvePpdCallback& cb) override {
+ // Do a sanity check here, so we can assumed |reference| is well-formed in
+ // the rest of this class.
+ if (!PpdReferenceIsWellFormed(reference)) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(cb, PpdProvider::INTERNAL_ERROR, ""));
return;
}
+ // First step, check the cache. If the cache lookup fails, we'll (try to)
+ // consult the server.
+ ppd_cache_->Find(PpdReferenceToCacheKey(reference),
+ base::Bind(&PpdProviderImpl::ResolvePpdCacheLookupDone,
+ weak_factory_.GetWeakPtr(), reference, cb));
+ }
- auto ppd_file = cache_->Store(fetch_data->ppd_reference, ppd_contents);
- if (!ppd_file) {
- // Failed to store.
- fetch_data->done_callback.Run(PpdProvider::INTERNAL_ERROR,
- base::FilePath());
- return;
- }
- fetch_data->done_callback.Run(PpdProvider::SUCCESS, ppd_file.value());
+ void CachePpd(const Printer::PpdReference& ppd_reference,
+ const std::string& ppd_contents) override {
+ ppd_cache_->Store(PpdReferenceToCacheKey(ppd_reference), ppd_contents,
+ base::Callback<void()>());
+ };
+
+ // Our only sources of long running ops are cache fetches and network fetches.
+ bool Idle() const override {
+ return ppd_cache_->Idle() && (fetcher_.get() == nullptr);
}
- // Called on the same thread as QueryAvailable() when the cache lookup is
- // done. If the cache satisfied the request, finish the Query, otherwise
- // start a URL request to satisfy the Query. This runs on the same thread as
- // QueryAvailable() was invoked on.
- void QueryAvailableCacheLookupDone(
- const PpdProvider::QueryAvailableCallback& done_callback,
- std::unique_ptr<base::Optional<PpdProvider::AvailablePrintersMap>>
- cache_result) {
- if (*cache_result) {
- done_callback.Run(PpdProvider::SUCCESS, cache_result->value());
- return;
- }
+ // Common handler that gets called whenever a fetch completes.
+ void OnURLFetchComplete(const net::URLFetcher* source) override {
+ switch (fetcher_target_) {
+ case FT_LOCALES:
+ OnLocalesFetchComplete();
+ break;
+ case FT_MANUFACTURERS:
+ OnManufacturersFetchComplete();
+ break;
+ case FT_PRINTERS:
+ OnPrintersFetchComplete();
+ break;
+ case FT_PPD_INDEX:
+ OnPpdIndexFetchComplete();
+ break;
+ case FT_PPD:
+ OnPpdFetchComplete();
+ break;
+ default:
+ LOG(DFATAL) << "Unknown fetch source";
+ };
+ MaybeStartFetch();
+ }
+
+ private:
+ // Return the URL used to look up the supported locales list.
+ GURL GetLocalesURL() {
+ return GURL(options_.ppd_server_root + "/metadata/locales.json");
+ }
- auto fetch_data = base::MakeUnique<QueryAvailableFetchData>();
- fetch_data->done_callback = done_callback;
-
- fetch_data->fetcher = net::URLFetcher::Create(GetQuirksServerPpdListURL(),
- net::URLFetcher::GET,
- &query_available_delegate_);
- fetch_data->fetcher->SetRequestContext(url_context_getter_.get());
- fetch_data->fetcher->SetLoadFlags(
- net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
- net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
- net::LOAD_DO_NOT_SEND_AUTH_DATA);
- auto* fetcher = fetch_data->fetcher.get();
- StoreQueryAvailableFetchData(std::move(fetch_data));
- fetcher->Start();
+ // Return the URL used to get the index of effective model -> ppd.
+ GURL GetPpdIndexURL() {
+ return GURL(options_.ppd_server_root + "/metadata/index.json");
}
- void OnQueryAvailableFetchComplete(const net::URLFetcher* source) {
- std::unique_ptr<QueryAvailableFetchData> fetch_data =
- GetQueryAvailableFetchData(source);
+ // Return the URL to get a localized manufacturers map.
+ GURL GetManufacturersURL(const std::string& locale) {
+ return GURL(base::StringPrintf("%s/metadata/manufacturers-%s.json",
+ options_.ppd_server_root.c_str(),
+ locale.c_str()));
+ }
+
+ // Return the URL used to get a list of printers from the manufacturer |ref|.
+ GURL GetPrintersURL(const ManufacturerReference& ref) {
+ return GURL(base::StringPrintf(
+ "%s/metadata/%s", options_.ppd_server_root.c_str(), ref.key.c_str()));
+ }
+ // Return the URL used to get a ppd with the given filename.
+ GURL GetPpdURL(const std::string& filename) {
+ return GURL(base::StringPrintf(
+ "%s/ppds/%s", options_.ppd_server_root.c_str(), filename.c_str()));
+ }
+
+ // Create and return a fetcher that has the usual (for this class) flags set
+ // and calls back to OnURLFetchComplete in this class when it finishes.
+ void StartFetch(const GURL& url, FetcherTarget target) {
+ DCHECK_EQ(nullptr, fetcher_.get());
+ fetcher_target_ = target;
+
+ fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
+ fetcher_->SetRequestContext(url_context_getter_.get());
+ fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA);
+ fetcher_->Start();
+ }
+
+ // Callback when the cache lookup for a ppd request finishes. If we hit in
+ // the cache, satisfy the resolution, otherwise kick it over to the fetcher
+ // queue to be grabbed from a server.
+ void ResolvePpdCacheLookupDone(const Printer::PpdReference& reference,
+ const ResolvePpdCallback& cb,
+ const PpdCache::FindResult& result) {
+ if (result.success) {
+ // Cache hit.
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, result.contents));
+ } else {
+ // Cache miss. Queue it to be satisfied by the fetcher queue.
+ ppd_resolution_queue_.push_back({reference, cb});
+ MaybeStartFetch();
+ }
+ }
+
+ // Handler for the completion of the locales fetch. This response should be a
+ // list of strings, each of which is a locale in which we can answer queries
+ // on the server. The server (should) guarantee that we get 'en' as an
+ // absolute minimum.
+ //
+ // Combine this information with the browser locale to figure out the best
+ // locale to use, and then start a fetch of the manufacturers map in that
+ // locale.
+ void OnLocalesFetchComplete() {
std::string contents;
- if (!ValidateAndGetResponseAsString(*source, &contents)) {
- // Something went wrong with the fetch.
- fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR,
- AvailablePrintersMap());
+ if (!ValidateAndGetResponseAsString(&contents)) {
+ FailQueuedMetadataResolutions(PpdProvider::SERVER_ERROR);
return;
}
+ auto top_list = base::ListValue::From(base::JSONReader::Read(contents));
- // The server gives us JSON in the form of a list of (manufacturer, list of
- // models) tuples.
- auto top_dict =
- base::DictionaryValue::From(base::JSONReader::Read(contents));
- const base::ListValue* top_list;
- if (top_dict == nullptr || !top_dict->GetList(kJSONTopListKey, &top_list)) {
- LOG(ERROR) << "Malformed response from quirks server";
- fetch_data->done_callback.Run(PpdProvider::SERVER_ERROR,
- AvailablePrintersMap());
+ if (top_list.get() == nullptr) {
+ // We got something malformed back.
+ FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
return;
}
- auto result = base::MakeUnique<PpdProvider::AvailablePrintersMap>();
+ // This should just be a simple list of locale strings.
+ std::vector<std::string> available_locales;
+ bool found_en = false;
for (const std::unique_ptr<base::Value>& entry : *top_list) {
- base::DictionaryValue* dict;
- std::string manufacturer;
- std::string model;
- base::ListValue* model_list;
- if (!entry->GetAsDictionary(&dict) ||
- !dict->GetString(kJSONManufacturer, &manufacturer) ||
- !dict->GetList(kJSONModelList, &model_list)) {
- LOG(ERROR) << "Unexpected contents in quirks server printer list.";
- // Just skip this entry instead of aborting the whole thing.
- continue;
+ std::string tmp;
+ // Locales should have at *least* a two-character country code. 100 is an
+ // arbitrary upper bound for length to protect against extreme bogosity.
+ if (!entry->GetAsString(&tmp) || tmp.size() < 2 || tmp.size() > 100) {
+ FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
+ return;
}
-
- std::vector<std::string>& dest = (*result)[manufacturer];
- for (const std::unique_ptr<base::Value>& model_value : *model_list) {
- if (model_value->GetAsString(&model)) {
- dest.push_back(model);
- } else {
- LOG(ERROR) << "Skipping unknown model for manufacturer "
- << manufacturer;
- }
+ if (tmp == "en") {
+ found_en = true;
}
+ available_locales.push_back(tmp);
}
- fetch_data->done_callback.Run(PpdProvider::SUCCESS, *result);
- if (!result->empty()) {
- cache_->StoreAvailablePrinters(std::move(result));
- } else {
- // An empty map means something is probably wrong; if we cache this map,
- // we'll have an empty map until the cache expires. So complain and
- // refuse to cache.
- LOG(ERROR) << "Available printers map is unexpectedly empty. Refusing "
- "to cache this.";
+ if (available_locales.empty() || !found_en) {
+ // We have no locales, or we didn't get an english locale (which is our
+ // ultimate fallback)
+ FailQueuedMetadataResolutions(PpdProvider::INTERNAL_ERROR);
+ return;
}
+ // Everything checks out, kick off the manufacturers list fetch in the
+ // appropriate locale.
+ std::string locale_to_use = GetBestLocale(available_locales);
+ StartFetch(GetManufacturersURL(locale_to_use), FT_MANUFACTURERS);
}
- private:
- void StoreResolveFetchData(std::unique_ptr<ResolveFetchData> fetch_data) {
- auto raw_ptr = fetch_data->fetcher.get();
- base::AutoLock lock(resolve_fetches_lock_);
- resolve_fetches_[raw_ptr] = std::move(fetch_data);
- }
-
- void StoreQueryAvailableFetchData(
- std::unique_ptr<QueryAvailableFetchData> fetch_data) {
- auto raw_ptr = fetch_data->fetcher.get();
- base::AutoLock lock(query_available_fetches_lock_);
- query_available_fetches_[raw_ptr] = std::move(fetch_data);
- }
-
- // Extract the result of a resolve url fetch into |ppd_contents| and
- // |last_updated time|. Returns true on success.
- bool ExtractResolveResponseData(const net::URLFetcher* source,
- const ResolveFetchData* fetch,
- std::string* ppd_contents,
- uint64_t* last_updated_time) {
- std::string contents;
- if (!ValidateAndGetResponseAsString(*source, &contents)) {
- LOG(WARNING) << "Response not validated";
- return false;
+ // Called when the |fetcher_| is expected have the results of a
+ // manufacturer map (which maps localized manufacturer names to keys for
+ // looking up printers from that manufacturer). Use this information to
+ // populate manufacturer_map_, and resolve all queued ResolveManufacturers()
+ // calls.
+ void OnManufacturersFetchComplete() {
+ DCHECK_EQ(nullptr, manufacturer_map_.get());
+ std::vector<std::pair<std::string, std::string>> contents;
+ auto code = ValidateAndParseJSONResponse(&contents);
skau 2017/01/05 20:38:46 Is this the result code?
Carlson 2017/01/26 21:59:36 Removed some autos to make this clearer.
+ if (code != PpdProvider::SUCCESS) {
+ FailQueuedMetadataResolutions(code);
+ return;
}
+ manufacturer_map_ = base::MakeUnique<ManufacturerMap>();
- auto dict = base::DictionaryValue::From(base::JSONReader::Read(contents));
- if (dict == nullptr) {
- LOG(WARNING) << "Response not a dictionary";
- return false;
+ for (const auto& entry : contents) {
+ ManufacturerReference ref;
+ ref.key = entry.second;
+ manufacturer_map_->insert({entry.first, ref});
}
- std::string last_updated_time_string;
- if (!dict->GetString(kJSONPPDKey, ppd_contents) ||
- !dict->GetString(kJSONLastUpdatedKey, &last_updated_time_string) ||
- !base::StringToUint64(last_updated_time_string, last_updated_time)) {
- // Malformed response. TODO(justincarlson) - LOG something here?
- return false;
+
+ // Complete any queued manufacturer resolutions.
+ for (const auto& cb : manufacturers_resolution_queue_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(cb, PpdProvider::SUCCESS, *manufacturer_map_));
}
+ manufacturers_resolution_queue_.clear();
+ }
- if (ppd_contents->size() > options_.max_ppd_contents_size_) {
- LOG(WARNING) << "PPD too large";
- // PPD is too big.
- //
- // Note -- if we ever add shared-ppd-sourcing, e.g. we may serve a ppd to
- // a user that's not from an explicitly trusted source, we should also
- // check *uncompressed* size here to head off zip-bombs (e.g. let's
- // compress 1GBs of zeros into a 900kb file and see what cups does when it
- // tries to expand that...)
- ppd_contents->clear();
- return false;
+ // The outstanding fetch associated with the front of
+ // |printers_resolution_queue_| finished, use the response to satisfy that
+ // ResolvePrinters() call.
+ void OnPrintersFetchComplete() {
+ DCHECK(!printers_resolution_queue_.empty());
+ std::vector<std::pair<std::string, std::string>> contents;
+ auto code = ValidateAndParseJSONResponse(&contents);
+ if (code != PpdProvider::SUCCESS) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(printers_resolution_queue_.front().second, code,
+ PrinterMap()));
+ } else {
+ // This should be a list of lists of 2-element strings, where the first
+ // element is the localized name of the printer and the second element
+ // is the canonical name of the printer.
+
+ // Create the printer map in the cache, and populate it.
+ auto& printer_map =
+ cached_printers_[printers_resolution_queue_.front().first.key];
+ for (const auto& entry : contents) {
+ Printer::PpdReference reference;
+ reference.effective_model = entry.second;
+ printer_map.insert({entry.first, reference});
+ }
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(printers_resolution_queue_.front().second,
+ PpdProvider::SUCCESS, printer_map));
}
- return base::Base64Decode(*ppd_contents, ppd_contents);
+ printers_resolution_queue_.pop_front();
}
- // Return the ResolveFetchData associated with |source|.
- std::unique_ptr<ResolveFetchData> GetResolveFetchData(
- const net::URLFetcher* source) {
- base::AutoLock l(resolve_fetches_lock_);
- auto it = resolve_fetches_.find(source);
- CHECK(it != resolve_fetches_.end());
- auto ret = std::move(it->second);
- resolve_fetches_.erase(it);
- return ret;
+ // Called when |fetcher_| should have just received the index mapping
+ // effective model name to ppd filenames. Use this to populate
+ // |cached_ppd_index_|.
+ void OnPpdIndexFetchComplete() {
+ std::vector<std::pair<std::string, std::string>> contents;
+ auto code = ValidateAndParseJSONResponse(&contents);
+ if (code != PpdProvider::SUCCESS) {
+ FailQueuedServerPpdResolutions(code);
+ } else {
+ cached_ppd_index_ =
+ base::MakeUnique<std::unordered_map<std::string, std::string>>();
+ // This should be a list of lists of 2-element strings, where the first
+ // element is the effective model name of the printer and the second
+ // element is the filename of the ppd.
+ for (const auto& entry : contents) {
+ cached_ppd_index_->insert(entry);
+ }
+ }
}
- // Return the QueryAvailableFetchData associated with |source|.
- std::unique_ptr<QueryAvailableFetchData> GetQueryAvailableFetchData(
- const net::URLFetcher* source) {
- base::AutoLock lock(query_available_fetches_lock_);
- auto it = query_available_fetches_.find(source);
- CHECK(it != query_available_fetches_.end()) << "Fetch data not found!";
- auto fetch_data = std::move(it->second);
- query_available_fetches_.erase(it);
- return fetch_data;
+ // This is called when |fetcher_| should have just downloaded a ppd. If we
+ // downloaded something successfully, use it to satisfy the front of the ppd
+ // resolution queue, otherwise fail out that resolution.
+ void OnPpdFetchComplete() {
+ DCHECK(!ppd_resolution_queue_.empty());
+ std::string contents;
+ if (!ValidateAndGetResponseAsString(&contents) ||
+ contents.size() > options_.max_ppd_contents_size_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(ppd_resolution_queue_.front().second,
+ PpdProvider::SERVER_ERROR, std::string()));
+ } else {
+ ppd_cache_->Store(
+ PpdReferenceToCacheKey(ppd_resolution_queue_.front().first), contents,
+ base::Callback<void()>());
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(ppd_resolution_queue_.front().second,
+ PpdProvider::SUCCESS, contents));
+ }
+ ppd_resolution_queue_.pop_front();
}
- // Trivial wrappers around PpdCache::Find() and
- // PpdCache::FindAvailablePrinters(). We need these wrappers both because we
- // use weak_ptrs to manage lifetime, and so we need to bind callbacks to
- // *this*, and because weak_ptr's preclude return values in posted tasks, so
- // we have to use a parameter to return state.
- void ResolveDoCacheLookup(
- const Printer::PpdReference& reference,
- base::Optional<base::FilePath>* cache_result) const {
- *cache_result = cache_->Find(reference);
+ // Something went wrong during metadata fetches. Fail all queued metadata
+ // resolutions with the given error code.
+ void FailQueuedMetadataResolutions(PpdProvider::CallbackResultCode code) {
+ for (const auto& cb : manufacturers_resolution_queue_) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(cb, code, ManufacturerMap()));
+ }
+ manufacturers_resolution_queue_.clear();
}
- void QueryAvailableDoCacheLookup(
- base::Optional<PpdProvider::AvailablePrintersMap>* cache_result) const {
- *cache_result = cache_->FindAvailablePrinters();
+ // Fail all server-based ppd resolutions inflight, because we failed to grab
+ // the necessary index data from the server. to the ppd server. Note we
skau 2017/01/05 20:38:46 'to the ppd server'?
Carlson 2017/01/26 21:59:36 Sentence fragment. Good device. More later. (re
+ // leave any user-based ppd resolutions intact, as they don't depend on the
+ // data we're missing.
+ void FailQueuedServerPpdResolutions(PpdProvider::CallbackResultCode code) {
+ std::deque<std::pair<Printer::PpdReference, ResolvePpdCallback>>
+ filtered_queue;
+
+ for (const auto& entry : ppd_resolution_queue_) {
+ if (!entry.first.user_supplied_ppd_url.empty()) {
+ filtered_queue.push_back(entry);
+ } else {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(entry.second, code, std::string()));
+ }
+ }
+ ppd_resolution_queue_ = std::move(filtered_queue);
}
- // Callback that happens when the Resolve() cache lookup completes. If the
- // cache satisfied the request, finish the Resolve, otherwise start a URL
- // request to satisfy the request. This runs on the same thread as Resolve()
- // was invoked on.
- void ResolveCacheLookupDone(
- const Printer::PpdReference& ppd_reference,
- const PpdProvider::ResolveCallback& done_callback,
- std::unique_ptr<base::Optional<base::FilePath>> cache_result) {
- if (*cache_result) {
- // Cache hit. Schedule the callback now and return.
- done_callback.Run(PpdProvider::SUCCESS, cache_result->value());
- return;
+ // Given a list of possible locale strings (e.g. 'en-GB'), determine which of
+ // them we should use to best serve results for the browser locale (which was
+ // given to us at construction time).
+ std::string GetBestLocale(const std::vector<std::string>& available_locales) {
+ // First look for an exact match. If we find one, just use that.
+ for (const std::string& available : available_locales) {
+ if (available == browser_locale_) {
+ return available;
+ }
}
- // We don't have a way to automatically resolve user-supplied PPDs yet. So
- // if we have one specified, and it's not cached, we fail out rather than
- // fall back to quirks-server based resolution. The reasoning here is that
- // if the user has specified a PPD when a quirks-server one exists, it
- // probably means the quirks server one doesn't work for some reason, so we
- // shouldn't silently use it.
- if (!ppd_reference.user_supplied_ppd_url.empty()) {
- done_callback.Run(PpdProvider::NOT_FOUND, base::FilePath());
- return;
+ // Next, look for an available locale that is a parent of browser_locale_.
+ // Return the most specific one. For example, if we want 'en-GB-foo' and we
+ // don't have an exact match, but we do have 'en-GB' and 'en', we will
+ // return 'en-GB' -- the most specific match which is a parent of the
+ // requested locale.
+ size_t best_len = 0;
+ size_t best_idx = -1;
+ for (size_t i = 0; i < available_locales.size(); ++i) {
+ const std::string& available = available_locales[i];
+ if (base::StringPiece(browser_locale_).starts_with(available + "-") &&
+ available.size() > best_len) {
+ best_len = available.size();
+ best_idx = i;
+ }
+ }
+ if (best_idx != static_cast<size_t>(-1)) {
+ return available_locales[best_idx];
}
- auto fetch_data = base::MakeUnique<ResolveFetchData>();
- fetch_data->ppd_reference = ppd_reference;
- fetch_data->done_callback = done_callback;
- fetch_data->fetcher =
- net::URLFetcher::Create(GetQuirksServerPpdLookupURL(ppd_reference),
- net::URLFetcher::GET, &resolve_delegate_);
-
- fetch_data->fetcher->SetRequestContext(url_context_getter_.get());
- fetch_data->fetcher->SetLoadFlags(
- net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
- net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
- net::LOAD_DO_NOT_SEND_AUTH_DATA);
- auto* fetcher = fetch_data->fetcher.get();
- StoreResolveFetchData(std::move(fetch_data));
- fetcher->Start();
- }
+ // Last chance for a match, look for the locale that matches the *most*
+ // pieces of locale_, with ties broken by being least specific. So for
+ // example, if we have 'es-GB', 'es-GB-foo' but no 'es' available, and we're
+ // requesting something for 'es', we'll get back 'es-GB' -- the least
+ // specific thing that matches some of the locale.
+ std::vector<base::StringPiece> browser_locale_pieces =
+ base::SplitStringPiece(browser_locale_, "-", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ size_t best_match_size = 0;
+ size_t best_match_specificity;
+ best_idx = -1;
+ for (size_t i = 0; i < available_locales.size(); ++i) {
+ const std::string& available = available_locales[i];
+ std::vector<base::StringPiece> available_pieces = base::SplitStringPiece(
+ available, "-", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ size_t match_size = 0;
+ for (; match_size < available_pieces.size() &&
+ match_size < browser_locale_pieces.size();
+ ++match_size) {
+ if (available_pieces[match_size] != browser_locale_pieces[match_size]) {
+ break;
+ }
+ }
+ if (match_size > 0 &&
+ (best_idx == static_cast<size_t>(-1) ||
+ match_size > best_match_size ||
+ (match_size == best_match_size &&
+ available_pieces.size() < best_match_specificity))) {
+ best_idx = i;
+ best_match_size = match_size;
+ best_match_specificity = available_pieces.size();
+ }
+ }
+ if (best_idx != static_cast<size_t>(-1)) {
+ return available_locales[best_idx];
+ }
- // Generate a url to look up a manufacturer/model from the quirks server
- GURL GetQuirksServerPpdLookupURL(
- const Printer::PpdReference& ppd_reference) const {
- return GURL(base::StringPrintf(
- "https://%s/v2/printer/manufacturers/%s/models/%s?key=%s",
- options_.quirks_server.c_str(),
- ppd_reference.effective_manufacturer.c_str(),
- ppd_reference.effective_model.c_str(), api_key_.c_str()));
+ // Everything else failed. Throw up our hands and default to english.
+ return "en";
}
- // Generate a url to ask for the full supported printer list from the quirks
- // server.
- GURL GetQuirksServerPpdListURL() const {
- return GURL(base::StringPrintf("https://%s/v2/printer/list?key=%s",
- options_.quirks_server.c_str(),
- api_key_.c_str()));
+ // If |fetcher| succeeded in its fetch, get the response in |response| and
+ // return true, otherwise return false. In all cases, resets |fetcher_|.
+ bool ValidateAndGetResponseAsString(std::string* contents) {
+ bool ret =
+ ((fetcher_->GetStatus().status() == net::URLRequestStatus::SUCCESS) &&
+ (fetcher_->GetResponseCode() == net::HTTP_OK) &&
+ fetcher_->GetResponseAsString(contents));
+ fetcher_.reset();
+ return ret;
}
- // If |fetcher| succeeded in its fetch, get the response in |response| and
- // return true, otherwise return false.
- bool ValidateAndGetResponseAsString(const net::URLFetcher& fetcher,
- std::string* contents) {
- return (fetcher.GetStatus().status() == net::URLRequestStatus::SUCCESS) &&
- (fetcher.GetResponseCode() == net::HTTP_OK) &&
- fetcher.GetResponseAsString(contents);
+ // Many of our metadata fetches happens to be in the form of a JSON
+ // list-of-lists-of-2-strings. So this just attempts to parse a JSON reply to
+ // |fetcher| into the passed contents vector. A return code of SUCCESS means
+ // the JSON was formatted as expected and we've parsed it into |contents|. On
+ // error the contents of |contents| cleared.
+ PpdProvider::CallbackResultCode ValidateAndParseJSONResponse(
+ std::vector<std::pair<std::string, std::string>>* contents) {
+ contents->clear();
+ std::string buffer;
+ if (!ValidateAndGetResponseAsString(&buffer)) {
+ return PpdProvider::SERVER_ERROR;
+ }
+ auto top_list = base::ListValue::From(base::JSONReader::Read(buffer));
+
+ if (top_list.get() == nullptr) {
+ // We got something malformed back.
+ return PpdProvider::INTERNAL_ERROR;
+ }
+ for (const auto& entry : *top_list) {
+ base::ListValue* sub_list;
+ contents->push_back({});
+ if (!entry->GetAsList(&sub_list) || sub_list->GetSize() != 2 ||
+ !sub_list->GetString(0, &contents->back().first) ||
+ !sub_list->GetString(1, &contents->back().second)) {
+ contents->clear();
+ return PpdProvider::INTERNAL_ERROR;
+ }
+ }
+ return PpdProvider::SUCCESS;
}
- // API key for accessing quirks server.
- const std::string api_key_;
+ // Map from (localized) manufacturers to ManufacturerReferences used to get
+ // printer lists. null until populated.
+ std::unique_ptr<ManufacturerMap> manufacturer_map_;
- scoped_refptr<net::URLRequestContextGetter> url_context_getter_;
- scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
- std::unique_ptr<PpdCache> cache_;
+ // Map from manufacturer reference to PrinterMap for that manufacturer,
+ // populated lazily, on demand.
+ std::unordered_map<std::string, PrinterMap> cached_printers_;
- // Construction-time options, immutable.
- const PpdProvider::Options options_;
+ // Cached contents of the server index, which maps
+ // PpdReference::effective_models to urls for the corresponding ppd. Null
+ // until we have fetched the index.
+ std::unique_ptr<std::unordered_map<std::string, std::string>>
+ cached_ppd_index_;
- ResolveURLFetcherDelegate resolve_delegate_;
- QueryAvailableURLFetcherDelegate query_available_delegate_;
+ // Queued ResolveManufacturers() calls. We will simultaneously resolve
+ // all queued requests, so no need for a deque here.
+ std::vector<ResolveManufacturersCallback> manufacturers_resolution_queue_;
- // Active resolve fetches and associated lock.
- std::unordered_map<const net::URLFetcher*, std::unique_ptr<ResolveFetchData>>
- resolve_fetches_;
- base::Lock resolve_fetches_lock_;
+ // Queued ResolvePrinters() calls.
+ std::deque<std::pair<ManufacturerReference, ResolvePrintersCallback>>
+ printers_resolution_queue_;
- // Active QueryAvailable() fetches and associated lock.
- std::unordered_map<const net::URLFetcher*,
- std::unique_ptr<QueryAvailableFetchData>>
- query_available_fetches_;
- base::Lock query_available_fetches_lock_;
+ // Queued ResolvePpd() requests.
+ std::deque<std::pair<Printer::PpdReference, ResolvePpdCallback>>
+ ppd_resolution_queue_;
- base::WeakPtrFactory<PpdProviderImpl> weak_factory_;
-};
+ // If the fetcher is active, what's it fetching?
+ FetcherTarget fetcher_target_;
-void ResolveURLFetcherDelegate::OnURLFetchComplete(
- const net::URLFetcher* source) {
- parent_->OnResolveFetchComplete(source);
-}
+ // Fetcher used for all network fetches. This is explicitly reset() when
+ // a fetch has been processed.
+ std::unique_ptr<net::URLFetcher> fetcher_;
-void QueryAvailableURLFetcherDelegate::OnURLFetchComplete(
- const net::URLFetcher* source) {
- parent_->OnQueryAvailableFetchComplete(source);
-}
+ // Locale of the browser, as returned by
+ // BrowserContext::GetApplicationLocale();
+ const std::string browser_locale_;
+
+ scoped_refptr<net::URLRequestContextGetter> url_context_getter_;
+
+ // Cache of ppd files.
+ scoped_refptr<PpdCache> ppd_cache_;
+
+ // Construction-time options, immutable.
+ const PpdProvider::Options options_;
+
+ base::WeakPtrFactory<PpdProviderImpl> weak_factory_;
+
+ protected:
+ ~PpdProviderImpl() override {}
+};
} // namespace
// static
-std::unique_ptr<PpdProvider> PpdProvider::Create(
- const std::string& api_key,
+scoped_refptr<PpdProvider> PpdProvider::Create(
+ const std::string& browser_locale,
scoped_refptr<net::URLRequestContextGetter> url_context_getter,
- scoped_refptr<base::SequencedTaskRunner> io_task_runner,
- std::unique_ptr<PpdCache> cache,
+ scoped_refptr<PpdCache> ppd_cache,
const PpdProvider::Options& options) {
- return base::MakeUnique<PpdProviderImpl>(
- api_key, url_context_getter, io_task_runner, std::move(cache), options);
+ return scoped_refptr<PpdProvider>(new PpdProviderImpl(
+ browser_locale, url_context_getter, ppd_cache, options));
}
-
} // namespace printing
} // namespace chromeos

Powered by Google App Engine
This is Rietveld 408576698