webstore_provider.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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 "chrome/common/extensions/extension_constants.h"
21#include "url/gurl.h"
22
23namespace app_list {
24
25namespace {
26
27const char kKeyResults[] = "results";
28const char kKeyId[] = "id";
29const char kKeyLocalizedName[] = "localized_name";
30const char kKeyIconUrl[] = "icon_url";
31const char kKeyIsPaid[] = "is_paid";
32const char kKeyItemType[] = "item_type";
33
34const char kPlatformAppType[] = "platform_app";
35const char kHostedAppType[] = "hosted_app";
36const char kLegacyPackagedAppType[] = "legacy_packaged_app";
37
38// Converts the item type string from the web store to an
39// extensions::Manifest::Type.
40extensions::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
55WebstoreProvider::WebstoreProvider(Profile* profile,
56                                   AppListControllerDelegate* controller)
57  :  WebserviceSearchProvider(profile),
58     controller_(controller){}
59
60WebstoreProvider::~WebstoreProvider() {}
61
62void 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
94void WebstoreProvider::Stop() {
95  if (webstore_search_)
96    webstore_search_->Stop();
97}
98
99void 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
108void 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
117void 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
149scoped_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