Chromium Code Reviews| 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 |