1 // Copyright 2013 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/ui/app_list/search/webstore/webstore_provider.h" 6 7 #include <string> 8 9 #include "base/bind.h" 10 #include "base/callback.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/values.h" 14 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/search/search.h" 17 #include "chrome/browser/ui/app_list/search/common/json_response_fetcher.h" 18 #include "chrome/browser/ui/app_list/search/search_webstore_result.h" 19 #include "chrome/browser/ui/app_list/search/webstore/webstore_result.h" 20 #include "extensions/common/extension_urls.h" 21 #include "url/gurl.h" 22 23 namespace app_list { 24 25 namespace { 26 27 const char kKeyResults[] = "results"; 28 const char kKeyId[] = "id"; 29 const char kKeyLocalizedName[] = "localized_name"; 30 const char kKeyIconUrl[] = "icon_url"; 31 const char kKeyIsPaid[] = "is_paid"; 32 const char kKeyItemType[] = "item_type"; 33 34 const char kPlatformAppType[] = "platform_app"; 35 const char kHostedAppType[] = "hosted_app"; 36 const char kLegacyPackagedAppType[] = "legacy_packaged_app"; 37 38 // Converts the item type string from the web store to an 39 // extensions::Manifest::Type. 40 extensions::Manifest::Type ParseItemType(const std::string& item_type_str) { 41 if (LowerCaseEqualsASCII(item_type_str, kPlatformAppType)) 42 return extensions::Manifest::TYPE_PLATFORM_APP; 43 44 if (LowerCaseEqualsASCII(item_type_str, kLegacyPackagedAppType)) 45 return extensions::Manifest::TYPE_LEGACY_PACKAGED_APP; 46 47 if (LowerCaseEqualsASCII(item_type_str, kHostedAppType)) 48 return extensions::Manifest::TYPE_HOSTED_APP; 49 50 return extensions::Manifest::TYPE_UNKNOWN; 51 } 52 53 } // namespace 54 55 WebstoreProvider::WebstoreProvider(Profile* profile, 56 AppListControllerDelegate* controller) 57 : WebserviceSearchProvider(profile), 58 controller_(controller){} 59 60 WebstoreProvider::~WebstoreProvider() {} 61 62 void WebstoreProvider::Start(const base::string16& query) { 63 ClearResults(); 64 if (!IsValidQuery(query)) { 65 query_.clear(); 66 return; 67 } 68 69 query_ = base::UTF16ToUTF8(query); 70 const CacheResult result = cache_->Get(WebserviceCache::WEBSTORE, query_); 71 if (result.second) { 72 ProcessWebstoreSearchResults(result.second); 73 if (!webstore_search_fetched_callback_.is_null()) 74 webstore_search_fetched_callback_.Run(); 75 if (result.first == FRESH) 76 return; 77 } 78 79 if (!webstore_search_) { 80 webstore_search_.reset(new JSONResponseFetcher( 81 base::Bind(&WebstoreProvider::OnWebstoreSearchFetched, 82 base::Unretained(this)), 83 profile_->GetRequestContext())); 84 } 85 86 StartThrottledQuery(base::Bind(&WebstoreProvider::StartQuery, 87 base::Unretained(this))); 88 89 // Add a placeholder result which when clicked will run the user's query in a 90 // browser. This placeholder is removed when the search results arrive. 91 Add(scoped_ptr<SearchResult>(new SearchWebstoreResult(profile_, query_))); 92 } 93 94 void WebstoreProvider::Stop() { 95 if (webstore_search_) 96 webstore_search_->Stop(); 97 } 98 99 void WebstoreProvider::StartQuery() { 100 // |query_| can be NULL when the query is scheduled but then canceled. 101 if (!webstore_search_ || query_.empty()) 102 return; 103 104 webstore_search_->Start(extension_urls::GetWebstoreJsonSearchUrl( 105 query_, g_browser_process->GetApplicationLocale())); 106 } 107 108 void WebstoreProvider::OnWebstoreSearchFetched( 109 scoped_ptr<base::DictionaryValue> json) { 110 ProcessWebstoreSearchResults(json.get()); 111 cache_->Put(WebserviceCache::WEBSTORE, query_, json.Pass()); 112 113 if (!webstore_search_fetched_callback_.is_null()) 114 webstore_search_fetched_callback_.Run(); 115 } 116 117 void WebstoreProvider::ProcessWebstoreSearchResults( 118 const base::DictionaryValue* json) { 119 const base::ListValue* result_list = NULL; 120 if (!json || 121 !json->GetList(kKeyResults, &result_list) || 122 !result_list || 123 result_list->empty()) { 124 return; 125 } 126 127 bool first_result = true; 128 for (base::ListValue::const_iterator it = result_list->begin(); 129 it != result_list->end(); 130 ++it) { 131 const base::DictionaryValue* dict; 132 if (!(*it)->GetAsDictionary(&dict)) 133 continue; 134 135 scoped_ptr<SearchResult> result(CreateResult(*dict)); 136 if (!result) 137 continue; 138 139 if (first_result) { 140 // Clears "search in webstore" place holder results. 141 ClearResults(); 142 first_result = false; 143 } 144 145 Add(result.Pass()); 146 } 147 } 148 149 scoped_ptr<ChromeSearchResult> WebstoreProvider::CreateResult( 150 const base::DictionaryValue& dict) { 151 scoped_ptr<ChromeSearchResult> result; 152 153 std::string app_id; 154 std::string localized_name; 155 std::string icon_url_string; 156 bool is_paid = false; 157 if (!dict.GetString(kKeyId, &app_id) || 158 !dict.GetString(kKeyLocalizedName, &localized_name) || 159 !dict.GetString(kKeyIconUrl, &icon_url_string) || 160 !dict.GetBoolean(kKeyIsPaid, &is_paid)) { 161 return result.Pass(); 162 } 163 164 GURL icon_url(icon_url_string); 165 if (!icon_url.is_valid()) 166 return result.Pass(); 167 168 std::string item_type_string; 169 dict.GetString(kKeyItemType, &item_type_string); 170 extensions::Manifest::Type item_type = ParseItemType(item_type_string); 171 172 result.reset(new WebstoreResult(profile_, 173 app_id, 174 localized_name, 175 icon_url, 176 is_paid, 177 item_type, 178 controller_)); 179 return result.Pass(); 180 } 181 182 } // namespace app_list 183