history_data_store.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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/history_data_store.h"
6
7#include "base/callback.h"
8#include "base/json/json_file_value_serializer.h"
9#include "base/json/json_string_value_serializer.h"
10#include "base/logging.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/task_runner_util.h"
13#include "base/threading/sequenced_worker_pool.h"
14#include "base/values.h"
15#include "content/public/browser/browser_thread.h"
16
17using content::BrowserThread;
18
19namespace app_list {
20
21namespace {
22
23const char kKeyVersion[] = "version";
24const char kCurrentVersion[] = "1";
25
26const char kKeyAssociations[] = "associations";
27const char kKeyPrimary[] = "p";
28const char kKeySecondary[] = "s";
29const char kKeyUpdateTime[] = "t";
30
31// Extracts strings from ListValue.
32void GetSecondary(const base::ListValue* list,
33                  HistoryData::SecondaryDeque* secondary) {
34  HistoryData::SecondaryDeque results;
35  for (base::ListValue::const_iterator it = list->begin();
36       it != list->end(); ++it) {
37    std::string str;
38    if (!(*it)->GetAsString(&str))
39      return;
40
41    results.push_back(str);
42  }
43
44  secondary->swap(results);
45}
46
47// V1 format json dictionary:
48//  {
49//    "version": "1",
50//    "associations": {
51//      "user typed query": {
52//        "p" : "result id of primary association",
53//        "s" : [
54//                "result id of 1st (oldest) secondary association",
55//                ...
56//                "result id of the newest secondary association"
57//              ],
58//        "t" : "last_update_timestamp"
59//      },
60//      ...
61//    }
62//  }
63scoped_ptr<HistoryData::Associations> Parse(const base::DictionaryValue& dict) {
64  std::string version;
65  if (!dict.GetStringWithoutPathExpansion(kKeyVersion, &version) ||
66      version != kCurrentVersion) {
67    return scoped_ptr<HistoryData::Associations>();
68  }
69
70  const base::DictionaryValue* assoc_dict = NULL;
71  if (!dict.GetDictionaryWithoutPathExpansion(kKeyAssociations, &assoc_dict) ||
72      !assoc_dict) {
73    return scoped_ptr<HistoryData::Associations>();
74  }
75
76  scoped_ptr<HistoryData::Associations> data(new HistoryData::Associations);
77  for (base::DictionaryValue::Iterator it(*assoc_dict);
78       !it.IsAtEnd(); it.Advance()) {
79    const base::DictionaryValue* entry_dict = NULL;
80    if (!it.value().GetAsDictionary(&entry_dict))
81      continue;
82
83    std::string primary;
84    std::string update_time_string;
85    if (!entry_dict->GetStringWithoutPathExpansion(kKeyPrimary, &primary) ||
86        !entry_dict->GetStringWithoutPathExpansion(kKeyUpdateTime,
87                                                   &update_time_string)) {
88      continue;
89    }
90
91    const base::ListValue* secondary_list = NULL;
92    HistoryData::SecondaryDeque secondary;
93    if (entry_dict->GetListWithoutPathExpansion(kKeySecondary, &secondary_list))
94      GetSecondary(secondary_list, &secondary);
95
96    const std::string& query = it.key();
97    HistoryData::Data& association_data = (*data.get())[query];
98    association_data.primary = primary;
99    association_data.secondary.swap(secondary);
100
101    int64 update_time_val;
102    base::StringToInt64(update_time_string, &update_time_val);
103    association_data.update_time =
104        base::Time::FromInternalValue(update_time_val);
105  }
106
107  return data.Pass();
108}
109
110// An empty callback used to ensure file tasks are cleared.
111void EmptyCallback() {}
112
113}  // namespace
114
115HistoryDataStore::HistoryDataStore(const base::FilePath& data_file)
116    : data_file_(data_file) {
117  std::string token("app-launcher-history-data-store");
118  token.append(data_file.AsUTF8Unsafe());
119
120  // Uses a SKIP_ON_SHUTDOWN file task runner because losing a couple
121  // associations is better than blocking shutdown.
122  base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
123  file_task_runner_ = pool->GetSequencedTaskRunnerWithShutdownBehavior(
124      pool->GetNamedSequenceToken(token),
125      base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
126  writer_.reset(
127      new base::ImportantFileWriter(data_file, file_task_runner_.get()));
128
129  cached_json_.reset(new base::DictionaryValue);
130  cached_json_->SetString(kKeyVersion, kCurrentVersion);
131  cached_json_->Set(kKeyAssociations, new base::DictionaryValue);
132}
133
134HistoryDataStore::~HistoryDataStore() {
135  Flush(OnFlushedCallback());
136}
137
138void HistoryDataStore::Flush(const OnFlushedCallback& on_flushed) {
139  if (writer_->HasPendingWrite())
140    writer_->DoScheduledWrite();
141
142  if (on_flushed.is_null())
143    return;
144
145  file_task_runner_->PostTaskAndReply(
146      FROM_HERE, base::Bind(&EmptyCallback), on_flushed);
147}
148
149void HistoryDataStore::Load(
150    const HistoryDataStore::OnLoadedCallback& on_loaded) {
151  base::PostTaskAndReplyWithResult(
152      file_task_runner_.get(),
153      FROM_HERE,
154      base::Bind(&HistoryDataStore::LoadOnBlockingPool, this),
155      on_loaded);
156}
157
158void HistoryDataStore::SetPrimary(const std::string& query,
159                                  const std::string& result) {
160  base::DictionaryValue* entry_dict = GetEntryDict(query);
161  entry_dict->SetWithoutPathExpansion(kKeyPrimary,
162                                      new base::StringValue(result));
163  writer_->ScheduleWrite(this);
164}
165
166void HistoryDataStore::SetSecondary(
167    const std::string& query,
168    const HistoryData::SecondaryDeque& results) {
169  scoped_ptr<base::ListValue> results_list(new base::ListValue);
170  for (size_t i = 0; i< results.size(); ++i)
171    results_list->AppendString(results[i]);
172
173  base::DictionaryValue* entry_dict = GetEntryDict(query);
174  entry_dict->SetWithoutPathExpansion(kKeySecondary, results_list.release());
175  writer_->ScheduleWrite(this);
176}
177
178void HistoryDataStore::SetUpdateTime(const std::string& query,
179                                     const base::Time& update_time) {
180  base::DictionaryValue* entry_dict = GetEntryDict(query);
181  entry_dict->SetWithoutPathExpansion(kKeyUpdateTime,
182                                      new base::StringValue(base::Int64ToString(
183                                          update_time.ToInternalValue())));
184  writer_->ScheduleWrite(this);
185}
186
187void HistoryDataStore::Delete(const std::string& query) {
188  base::DictionaryValue* assoc_dict = GetAssociationDict();
189  assoc_dict->RemoveWithoutPathExpansion(query, NULL);
190  writer_->ScheduleWrite(this);
191}
192
193base::DictionaryValue* HistoryDataStore::GetAssociationDict() {
194  base::DictionaryValue* assoc_dict = NULL;
195  CHECK(cached_json_->GetDictionary(kKeyAssociations, &assoc_dict) &&
196        assoc_dict);
197
198  return assoc_dict;
199}
200
201base::DictionaryValue* HistoryDataStore::GetEntryDict(
202    const std::string& query) {
203  base::DictionaryValue* assoc_dict = GetAssociationDict();
204
205  base::DictionaryValue* entry_dict = NULL;
206  if (!assoc_dict->GetDictionaryWithoutPathExpansion(query, &entry_dict)) {
207    // Creates one if none exists. Ownership is taken in the set call after.
208    entry_dict = new base::DictionaryValue;
209    assoc_dict->SetWithoutPathExpansion(query, entry_dict);
210  }
211
212  return entry_dict;
213}
214
215scoped_ptr<HistoryData::Associations> HistoryDataStore::LoadOnBlockingPool() {
216  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
217
218  int error_code = JSONFileValueSerializer::JSON_NO_ERROR;
219  std::string error_message;
220  JSONFileValueSerializer serializer(data_file_);
221  base::Value* value = serializer.Deserialize(&error_code, &error_message);
222  base::DictionaryValue* dict_value = NULL;
223  if (error_code != JSONFileValueSerializer::JSON_NO_ERROR ||
224      !value ||
225      !value->GetAsDictionary(&dict_value) ||
226      !dict_value) {
227    return scoped_ptr<HistoryData::Associations>();
228  }
229
230  cached_json_.reset(dict_value);
231  return Parse(*dict_value).Pass();
232}
233
234bool HistoryDataStore::SerializeData(std::string* data) {
235  JSONStringValueSerializer serializer(data);
236  serializer.set_pretty_print(true);
237  return serializer.Serialize(*cached_json_.get());
238}
239
240}  // namespace app_list
241