| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/intents/cws_intents_registry.h" | |
| 6 | |
| 7 #include "base/callback.h" | |
| 8 #include "base/json/json_string_value_serializer.h" | |
| 9 #include "base/memory/scoped_ptr.h" | |
| 10 #include "base/stl_util.h" | |
| 11 #include "base/string16.h" | |
| 12 #include "base/utf_string_conversions.h" | |
| 13 #include "chrome/common/extensions/extension_l10n_util.h" | |
| 14 #include "chrome/common/extensions/message_bundle.h" | |
| 15 #include "chrome/browser/net/chrome_url_request_context.h" | |
| 16 #include "chrome/browser/webdata/web_data_service.h" | |
| 17 #include "google_apis/google_api_keys.h" | |
| 18 #include "net/base/load_flags.h" | |
| 19 #include "net/base/mime_util.h" | |
| 20 #include "net/base/url_util.h" | |
| 21 #include "net/url_request/url_fetcher.h" | |
| 22 | |
| 23 namespace { | |
| 24 | |
| 25 // Limit for the number of suggestions we fix from CWS. Ideally, the registry | |
| 26 // simply get all of them, but there is a) chunking on the CWS side, and b) | |
| 27 // there is a cost with suggestions fetched. (Network overhead for favicons, | |
| 28 // roundtrips to registry to check if installed). | |
| 29 // | |
| 30 // Since the picker limits the number of suggestions displayed to 5, 15 means | |
| 31 // the suggestion list only has the potential to be shorter than that once the | |
| 32 // user has at least 10 installed handlers for the particular action/type. | |
| 33 // | |
| 34 // TODO(groby): Adopt number of suggestions dynamically so the picker can | |
| 35 // always display 5 suggestions unless there are less than 5 viable extensions | |
| 36 // in the CWS. | |
| 37 const char kMaxSuggestions[] = "15"; | |
| 38 | |
| 39 // URL for CWS intents API. | |
| 40 const char kCWSIntentServiceURL[] = | |
| 41 "https://www.googleapis.com/chromewebstore/v1.1b/items/intent"; | |
| 42 | |
| 43 // Determines if a string is a candidate for localization. | |
| 44 bool ShouldLocalize(const std::string& value) { | |
| 45 std::string::size_type index = 0; | |
| 46 index = value.find(extensions::MessageBundle::kMessageBegin); | |
| 47 if (index == std::string::npos) | |
| 48 return false; | |
| 49 | |
| 50 index = value.find(extensions::MessageBundle::kMessageEnd, index); | |
| 51 return (index != std::string::npos); | |
| 52 } | |
| 53 | |
| 54 // Parses a JSON |response| from the CWS into a list of suggested extensions, | |
| 55 // stored in |intents|. |intents| must not be NULL. | |
| 56 void ParseResponse(const std::string& response, | |
| 57 CWSIntentsRegistry::IntentExtensionList* intents) { | |
| 58 std::string error; | |
| 59 scoped_ptr<Value> parsed_response; | |
| 60 JSONStringValueSerializer serializer(response); | |
| 61 parsed_response.reset(serializer.Deserialize(NULL, &error)); | |
| 62 if (parsed_response.get() == NULL) | |
| 63 return; | |
| 64 | |
| 65 DictionaryValue* response_dict = NULL; | |
| 66 if (!parsed_response->GetAsDictionary(&response_dict) || !response_dict) | |
| 67 return; | |
| 68 | |
| 69 ListValue* items; | |
| 70 if (!response_dict->GetList("items", &items)) | |
| 71 return; | |
| 72 | |
| 73 for (ListValue::const_iterator iter(items->begin()); | |
| 74 iter != items->end(); ++iter) { | |
| 75 DictionaryValue* item = static_cast<DictionaryValue*>(*iter); | |
| 76 | |
| 77 // All fields are mandatory - skip this result if any field isn't found. | |
| 78 CWSIntentsRegistry::IntentExtensionInfo info; | |
| 79 if (!item->GetString("id", &info.id)) | |
| 80 continue; | |
| 81 | |
| 82 if (!item->GetInteger("num_ratings", &info.num_ratings)) | |
| 83 continue; | |
| 84 | |
| 85 if (!item->GetDouble("average_rating", &info.average_rating)) | |
| 86 continue; | |
| 87 | |
| 88 if (!item->GetString("manifest", &info.manifest)) | |
| 89 continue; | |
| 90 | |
| 91 std::string manifest_utf8 = UTF16ToUTF8(info.manifest); | |
| 92 JSONStringValueSerializer manifest_serializer(manifest_utf8); | |
| 93 scoped_ptr<Value> manifest_value; | |
| 94 manifest_value.reset(manifest_serializer.Deserialize(NULL, &error)); | |
| 95 if (manifest_value.get() == NULL) | |
| 96 continue; | |
| 97 | |
| 98 DictionaryValue* manifest_dict; | |
| 99 if (!manifest_value->GetAsDictionary(&manifest_dict) || | |
| 100 !manifest_dict->GetString("name", &info.name)) | |
| 101 continue; | |
| 102 | |
| 103 string16 url_string; | |
| 104 if (!item->GetString("icon_url", &url_string)) | |
| 105 continue; | |
| 106 info.icon_url = GURL(url_string); | |
| 107 | |
| 108 // Need to parse CWS reply, since it is not pre-l10n'd. | |
| 109 ListValue* all_locales = NULL; | |
| 110 if (ShouldLocalize(UTF16ToUTF8(info.name)) && | |
| 111 item->GetList("locale_data", &all_locales)) { | |
| 112 std::map<std::string, std::string> localized_title; | |
| 113 | |
| 114 for (ListValue::const_iterator locale_iter(all_locales->begin()); | |
| 115 locale_iter != all_locales->end(); ++locale_iter) { | |
| 116 DictionaryValue* locale = static_cast<DictionaryValue*>(*locale_iter); | |
| 117 | |
| 118 std::string locale_id, title; | |
| 119 if (!locale->GetString("locale_string", &locale_id) || | |
| 120 !locale->GetString("title", &title)) | |
| 121 continue; | |
| 122 | |
| 123 localized_title[locale_id] = title; | |
| 124 } | |
| 125 | |
| 126 std::vector<std::string> valid_locales; | |
| 127 extension_l10n_util::GetAllFallbackLocales( | |
| 128 extension_l10n_util::CurrentLocaleOrDefault(), | |
| 129 "all", | |
| 130 &valid_locales); | |
| 131 for (std::vector<std::string>::iterator iter = valid_locales.begin(); | |
| 132 iter != valid_locales.end(); ++iter) { | |
| 133 if (localized_title.count(*iter)) { | |
| 134 info.name = UTF8ToUTF16(localized_title[*iter]); | |
| 135 break; | |
| 136 } | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 intents->push_back(info); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 } // namespace | |
| 145 | |
| 146 // Internal object representing all data associated with a single query. | |
| 147 struct CWSIntentsRegistry::IntentsQuery { | |
| 148 IntentsQuery(); | |
| 149 ~IntentsQuery(); | |
| 150 | |
| 151 // Underlying URL request query. | |
| 152 scoped_ptr<net::URLFetcher> url_fetcher; | |
| 153 | |
| 154 // The callback - invoked on completed retrieval. | |
| 155 ResultsCallback callback; | |
| 156 }; | |
| 157 | |
| 158 CWSIntentsRegistry::IntentsQuery::IntentsQuery() { | |
| 159 } | |
| 160 | |
| 161 CWSIntentsRegistry::IntentsQuery::~IntentsQuery() { | |
| 162 } | |
| 163 | |
| 164 CWSIntentsRegistry::IntentExtensionInfo::IntentExtensionInfo() | |
| 165 : num_ratings(0), | |
| 166 average_rating(0) { | |
| 167 } | |
| 168 | |
| 169 CWSIntentsRegistry::IntentExtensionInfo::~IntentExtensionInfo() { | |
| 170 } | |
| 171 | |
| 172 CWSIntentsRegistry::CWSIntentsRegistry(net::URLRequestContextGetter* context) | |
| 173 : request_context_(context) { | |
| 174 } | |
| 175 | |
| 176 CWSIntentsRegistry::~CWSIntentsRegistry() { | |
| 177 // Cancel all pending queries, since we can't handle them any more. | |
| 178 STLDeleteValues(&queries_); | |
| 179 } | |
| 180 | |
| 181 void CWSIntentsRegistry::OnURLFetchComplete(const net::URLFetcher* source) { | |
| 182 DCHECK(source); | |
| 183 | |
| 184 URLFetcherHandle handle = reinterpret_cast<URLFetcherHandle>(source); | |
| 185 QueryMap::iterator it = queries_.find(handle); | |
| 186 DCHECK(it != queries_.end()); | |
| 187 scoped_ptr<IntentsQuery> query(it->second); | |
| 188 DCHECK(query.get() != NULL); | |
| 189 queries_.erase(it); | |
| 190 | |
| 191 std::string response; | |
| 192 source->GetResponseAsString(&response); | |
| 193 | |
| 194 // TODO(groby): Do we really only accept 200, or any 2xx codes? | |
| 195 IntentExtensionList intents; | |
| 196 if (source->GetResponseCode() == 200) | |
| 197 ParseResponse(response, &intents); | |
| 198 | |
| 199 if (!query->callback.is_null()) | |
| 200 query->callback.Run(intents); | |
| 201 } | |
| 202 | |
| 203 void CWSIntentsRegistry::GetIntentServices(const string16& action, | |
| 204 const string16& mimetype, | |
| 205 const ResultsCallback& cb) { | |
| 206 scoped_ptr<IntentsQuery> query(new IntentsQuery); | |
| 207 query->callback = cb; | |
| 208 query->url_fetcher.reset(net::URLFetcher::Create( | |
| 209 0, BuildQueryURL(action,mimetype), net::URLFetcher::GET, this)); | |
| 210 | |
| 211 if (query->url_fetcher.get() == NULL) | |
| 212 return; | |
| 213 | |
| 214 query->url_fetcher->SetRequestContext(request_context_); | |
| 215 query->url_fetcher->SetLoadFlags( | |
| 216 net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES); | |
| 217 | |
| 218 URLFetcherHandle handle = reinterpret_cast<URLFetcherHandle>( | |
| 219 query->url_fetcher.get()); | |
| 220 queries_[handle] = query.release(); | |
| 221 queries_[handle]->url_fetcher->Start(); | |
| 222 } | |
| 223 | |
| 224 // static | |
| 225 GURL CWSIntentsRegistry::BuildQueryURL(const string16& action, | |
| 226 const string16& type) { | |
| 227 GURL request(kCWSIntentServiceURL); | |
| 228 request = net::AppendQueryParameter(request, "intent", UTF16ToUTF8(action)); | |
| 229 request = net::AppendQueryParameter(request, "mime_types", UTF16ToUTF8(type)); | |
| 230 request = net::AppendQueryParameter(request, "start_index", "0"); | |
| 231 request = net::AppendQueryParameter(request, "num_results", kMaxSuggestions); | |
| 232 request = net::AppendQueryParameter(request, "key", google_apis::GetAPIKey()); | |
| 233 | |
| 234 return request; | |
| 235 } | |
| OLD | NEW |