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/common/webservice_cache.h"
6
7#include "base/strings/string_number_conversions.h"
8#include "base/values.h"
9#include "content/public/browser/browser_context.h"
10
11namespace app_list {
12namespace {
13
14const unsigned int kWebserviceCacheMaxSize = 1000;
15const unsigned int kWebserviceCacheTimeLimitInMinutes = 1;
16
17const char kKeyResultTime[] = "time";
18const char kKeyResult[] = "result";
19
20const char kWebstoreQueryPrefix[] = "webstore:";
21const char kPeopleQueryPrefix[] = "people:";
22
23}  // namespace
24
25void WebserviceCache::CacheDeletor::operator()(Payload& payload) {
26  delete payload.result;
27}
28
29WebserviceCache::WebserviceCache(content::BrowserContext* context)
30    : cache_(Cache::NO_AUTO_EVICT),
31      cache_loaded_(false) {
32  const char kStoreDataFileName[] = "Webservice Search Cache";
33  const base::FilePath data_file =
34      context->GetPath().AppendASCII(kStoreDataFileName);
35  data_store_ = new DictionaryDataStore(data_file);
36  data_store_->Load(base::Bind(&WebserviceCache::OnCacheLoaded, AsWeakPtr()));
37}
38
39WebserviceCache::~WebserviceCache() {
40}
41
42const CacheResult WebserviceCache::Get(QueryType type,
43                                       const std::string& query) {
44  std::string typed_query = PrependType(type, query);
45  Cache::iterator iter = cache_.Get(typed_query);
46  if (iter != cache_.end()) {
47    if (base::Time::Now() - iter->second.time <=
48        base::TimeDelta::FromMinutes(kWebserviceCacheTimeLimitInMinutes)) {
49      return std::make_pair(FRESH, iter->second.result);
50    } else {
51      return std::make_pair(STALE, iter->second.result);
52    }
53  }
54  return std::make_pair(STALE, static_cast<base::DictionaryValue*>(NULL));
55}
56
57void WebserviceCache::Put(QueryType type,
58                          const std::string& query,
59                          scoped_ptr<base::DictionaryValue> result) {
60  if (result) {
61    std::string typed_query = PrependType(type, query);
62    Payload payload(base::Time::Now(), result.release());
63
64    cache_.Put(typed_query, payload);
65    // If the cache isn't loaded yet, we're fine with losing queries since
66    // a 1000 entry cache should load really quickly so the chance of a user
67    // already having typed a 3 character search before the cache has loaded is
68    // very unlikely.
69    if (cache_loaded_) {
70      data_store_->cached_dict()->Set(typed_query, DictFromPayload(payload));
71      data_store_->ScheduleWrite();
72      if (cache_.size() > kWebserviceCacheMaxSize)
73        TrimCache();
74    }
75  }
76}
77
78void WebserviceCache::OnCacheLoaded(scoped_ptr<base::DictionaryValue>) {
79  if (!data_store_->cached_dict())
80    return;
81
82  std::vector<std::string> cleanup_keys;
83  for (base::DictionaryValue::Iterator it(*data_store_->cached_dict());
84      !it.IsAtEnd();
85      it.Advance()) {
86    const base::DictionaryValue* payload_dict;
87    Payload payload;
88    if (!it.value().GetAsDictionary(&payload_dict) ||
89        !payload_dict ||
90        !PayloadFromDict(payload_dict, &payload)) {
91      // In case we don't have a valid payload associated with a given query,
92      // clean up that query from our data store.
93      cleanup_keys.push_back(it.key());
94      continue;
95    }
96    cache_.Put(it.key(), payload);
97  }
98
99  if (!cleanup_keys.empty()) {
100    for (size_t i = 0; i < cleanup_keys.size(); ++i)
101      data_store_->cached_dict()->Remove(cleanup_keys[i], NULL);
102    data_store_->ScheduleWrite();
103  }
104  cache_loaded_ = true;
105}
106
107bool WebserviceCache::PayloadFromDict(const base::DictionaryValue* dict,
108                                      Payload* payload) {
109  std::string time_string;
110  if (!dict->GetString(kKeyResultTime, &time_string))
111    return false;
112  const base::DictionaryValue* result;
113  if (!dict->GetDictionary(kKeyResult, &result))
114    return false;
115
116  int64 time_val;
117  base::StringToInt64(time_string, &time_val);
118
119  // The result dictionary will be owned by the cache, hence create a copy
120  // instead of returning the original reference. The new dictionary will be
121  // owned by our MRU cache.
122  *payload = Payload(base::Time::FromInternalValue(time_val),
123                     result->DeepCopy());
124  return true;
125}
126
127base::DictionaryValue* WebserviceCache::DictFromPayload(
128    const Payload& payload) {
129  base::DictionaryValue* dict = new base::DictionaryValue();
130  dict->SetString(kKeyResultTime, base::Int64ToString(
131      payload.time.ToInternalValue()));
132  // The payload will still keep ownership of it's result dict, hence put a
133  // a copy of the result dictionary here. This dictionary will be owned by
134  // data_store_->cached_dict().
135  dict->Set(kKeyResult, payload.result->DeepCopy());
136
137  return dict;
138}
139
140void WebserviceCache::TrimCache() {
141  for (Cache::size_type i = cache_.size(); i > kWebserviceCacheMaxSize; i--) {
142    Cache::reverse_iterator rbegin = cache_.rbegin();
143    data_store_->cached_dict()->Remove(rbegin->first, NULL);
144    cache_.Erase(rbegin);
145  }
146  data_store_->ScheduleWrite();
147}
148
149std::string WebserviceCache::PrependType(
150    QueryType type, const std::string& query) {
151  switch (type) {
152    case WEBSTORE:
153      return kWebstoreQueryPrefix + query;
154    case PEOPLE:
155      return kPeopleQueryPrefix + query;
156    default:
157      return query;
158  }
159}
160
161}  // namespace app_list
162