OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/interests/interests_retriever.h" |
| 6 |
| 7 #include "base/json/json_reader.h" |
| 8 #include "base/logging.h" |
| 9 #include "base/values.h" |
| 10 #include "net/base/load_flags.h" |
| 11 #include "net/http/http_status_code.h" |
| 12 #include "net/url_request/test_url_fetcher_factory.h" |
| 13 #include "net/url_request/url_fetcher.h" |
| 14 #include "net/url_request/url_fetcher_factory.h" |
| 15 #include "net/url_request/url_request_context_getter.h" |
| 16 #include "net/url_request/url_request_status.h" |
| 17 |
| 18 using net::FakeURLFetcher; |
| 19 using net::URLFetcher; |
| 20 using net::URLFetcherDelegate; |
| 21 using net::URLRequestContextGetter; |
| 22 using net::URLRequestStatus; |
| 23 using net::URLFetcherFactory; |
| 24 |
| 25 namespace { |
| 26 |
| 27 const int kNumRetries = 1; |
| 28 const char kIdInterests[] = "interest"; |
| 29 const char kIdInterestName[] = "name"; |
| 30 const char kIdInterestImageUrl[] = "imageUrl"; |
| 31 const char kIdInterestRelevance[] = "relevance"; |
| 32 |
| 33 const char kInterestsUrl[] = "https://www.googleapis.com/TBD/v1/interests"; |
| 34 |
| 35 const char kFakeResponse[] = |
| 36 "{" |
| 37 " \"interest\": [" |
| 38 " {\n" |
| 39 " \"name\": \"Munich\",\n" |
| 40 " \"imageUrl\": " |
| 41 "\"https://usercontent.googleapis.com/freebase/v1/image/m/" |
| 42 "02h6_6p?maxwidth=200&maxheight=200&mode=fill\"," |
| 43 " \"relevance\": 0.85" |
| 44 " }," |
| 45 " {" |
| 46 " \"name\": \"Defense of the Ancients\"," |
| 47 " \"imageUrl\": " |
| 48 "\"https://usercontent.googleapis.com/freebase/v1/image/m/" |
| 49 "073pnt?maxwidth=200&maxheight=200&mode=fill\"," |
| 50 " \"relevance\": 1" |
| 51 " }," |
| 52 " {" |
| 53 " \"name\": \"Google\"," |
| 54 " \"imageUrl\": " |
| 55 "\"https://usercontent.googleapis.com/freebase/v1/image/m/" |
| 56 "045c7b?maxwidth=200&maxheight=200&mode=fill&A\"," |
| 57 " \"relevance\": 0.9" |
| 58 " }," |
| 59 " {" |
| 60 " \"name\": \"Google Chrome\"," |
| 61 " \"imageUrl\": " |
| 62 "\"https://usercontent.googleapis.com/freebase/v1/image/m/" |
| 63 "04j7cyf?maxwidth=200&maxheight=200&mode=fill\"," |
| 64 " \"relevance\": 0.98" |
| 65 " }" |
| 66 " ]" |
| 67 "}" |
| 68 ""; |
| 69 |
| 70 std::vector<InterestsRetriever::Interest> EmptyResponse() { |
| 71 return std::vector<InterestsRetriever::Interest>(); |
| 72 } |
| 73 |
| 74 } // namespace |
| 75 |
| 76 InterestsRetriever::InterestsRetriever( |
| 77 net::URLRequestContextGetter* url_request_context, |
| 78 const std::string& access_token, |
| 79 const InterestsRetriever::InterestsCallback& callback, |
| 80 URLFetcherFactory* url_fetcher_factory) |
| 81 : url_request_context_(url_request_context), |
| 82 access_token_(access_token), |
| 83 callback_(callback), |
| 84 url_fetcher_factory_(url_fetcher_factory) { |
| 85 fetcher_ = CreateFetcher(); |
| 86 |
| 87 // Setup fetcher |
| 88 fetcher_->SetRequestContext(url_request_context_); |
| 89 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| 90 net::LOAD_DO_NOT_SAVE_COOKIES); |
| 91 fetcher_->SetAutomaticallyRetryOnNetworkChanges(kNumRetries); |
| 92 |
| 93 // Add oauth access token |
| 94 fetcher_->AddExtraRequestHeader(std::string("Authorization: Bearer ") + |
| 95 access_token_); |
| 96 |
| 97 fetcher_->Start(); |
| 98 } |
| 99 |
| 100 scoped_ptr<URLFetcher> InterestsRetriever::CreateFetcher() { |
| 101 // The API is not accessible yet. So a static response is provided. |
| 102 // return URLFetcher::Create(GURL(kInterestsUrl), URLFetcher::GET, this); |
| 103 |
| 104 // If a Factory is provided use it. This is needed for testing. |
| 105 if (url_fetcher_factory_) { |
| 106 return url_fetcher_factory_->CreateURLFetcher(0, GURL(kInterestsUrl), |
| 107 URLFetcher::GET, this); |
| 108 |
| 109 } else { |
| 110 return scoped_ptr<URLFetcher>(new FakeURLFetcher( |
| 111 GURL(kInterestsUrl), this, std::string(kFakeResponse), net::HTTP_OK, |
| 112 net::URLRequestStatus::SUCCESS)); |
| 113 } |
| 114 } |
| 115 |
| 116 void InterestsRetriever::OnURLFetchComplete(const net::URLFetcher* source) { |
| 117 const URLRequestStatus& status = source->GetStatus(); |
| 118 |
| 119 if (!status.is_success()) { |
| 120 DLOG(WARNING) << "URL request failed!"; |
| 121 callback_.Run(EmptyResponse()); |
| 122 return; |
| 123 } |
| 124 |
| 125 std::string response_body; |
| 126 source->GetResponseAsString(&response_body); |
| 127 |
| 128 callback_.Run(ExtractInterests(response_body)); |
| 129 } |
| 130 |
| 131 std::vector<InterestsRetriever::Interest> InterestsRetriever::ExtractInterests( |
| 132 const std::string& response) { |
| 133 scoped_ptr<base::Value> value = base::JSONReader::Read(response); |
| 134 |
| 135 const base::DictionaryValue* dict = nullptr; |
| 136 if (!value || !value->GetAsDictionary(&dict)) { |
| 137 DLOG(WARNING) << "ExtractInterests failed to parse global dictionary"; |
| 138 return EmptyResponse(); |
| 139 } |
| 140 |
| 141 const base::ListValue* interests_list = nullptr; |
| 142 std::vector<Interest> res; |
| 143 |
| 144 if (!dict->GetList(kIdInterests, &interests_list)) { |
| 145 DLOG(WARNING) << "ExtractInterests failed to parse interests list"; |
| 146 return EmptyResponse(); |
| 147 } |
| 148 |
| 149 for (const base::Value* entry : *interests_list) { |
| 150 const base::DictionaryValue* interest_dict = nullptr; |
| 151 if (!entry->GetAsDictionary(&interest_dict)) { |
| 152 DLOG(WARNING) << "ExtractInterests failed to parse interest dictionary"; |
| 153 return EmptyResponse(); |
| 154 } |
| 155 |
| 156 std::string name; |
| 157 if (!interest_dict->GetString(kIdInterestName, &name)) { |
| 158 DLOG(WARNING) << "ExtractInterests failed to parse interest name"; |
| 159 return EmptyResponse(); |
| 160 } |
| 161 |
| 162 std::string image_url; |
| 163 if (!interest_dict->GetString(kIdInterestImageUrl, &image_url)) { |
| 164 // image_url is allowed to be missing. |
| 165 // |
| 166 // However this is still logged as a warning, since, currently, the server |
| 167 // should always provide an image_url. |
| 168 // |
| 169 DLOG(WARNING) << "ExtractInterests failed to parse interest image URL"; |
| 170 } |
| 171 |
| 172 double relevance; |
| 173 if (!interest_dict->GetDouble(kIdInterestRelevance, &relevance)) { |
| 174 DLOG(WARNING) << "ExtractInterests failed to parse interest relevance"; |
| 175 return EmptyResponse(); |
| 176 } |
| 177 |
| 178 res.push_back(Interest{name, image_url, relevance}); |
| 179 } |
| 180 |
| 181 return res; |
| 182 } |
| 183 |
| 184 InterestsRetriever::~InterestsRetriever() {} |
| 185 |
| 186 std::string InterestsRetriever::GetFakeResponseForTest() { |
| 187 return kFakeResponse; |
| 188 } |
| 189 |
| 190 bool InterestsRetriever::Interest::operator==(const Interest& interest) const { |
| 191 return name == interest.name && |
| 192 image_url == interest.image_url && |
| 193 relevance == interest.relevance; |
| 194 } |
OLD | NEW |