history_data_store.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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(new base::ImportantFileWriter(data_file, file_task_runner_));
127
128  cached_json_.reset(new base::DictionaryValue);
129  cached_json_->SetString(kKeyVersion, kCurrentVersion);
130  cached_json_->Set(kKeyAssociations, new base::DictionaryValue);
131}
132
133HistoryDataStore::~HistoryDataStore() {
134  Flush(OnFlushedCallback());
135}
136
137void HistoryDataStore::Flush(const OnFlushedCallback& on_flushed) {
138  if (writer_->HasPendingWrite())
139    writer_->DoScheduledWrite();
140
141  if (on_flushed.is_null())
142    return;
143
144  file_task_runner_->PostTaskAndReply(
145      FROM_HERE, base::Bind(&EmptyCallback), on_flushed);
146}
147
148void HistoryDataStore::Load(
149    const HistoryDataStore::OnLoadedCallback& on_loaded) {
150  base::PostTaskAndReplyWithResult(
151      file_task_runner_,
152      FROM_HERE,
153      base::Bind(&HistoryDataStore::LoadOnBlockingPool, this),
154      on_loaded);
155}
156
157void HistoryDataStore::SetPrimary(const std::string& query,
158                                  const std::string& result) {
159  base::DictionaryValue* entry_dict = GetEntryDict(query);
160  entry_dict->SetWithoutPathExpansion(kKeyPrimary,
161                                      new base::StringValue(result));
162  writer_->ScheduleWrite(this);
163}
164
165void HistoryDataStore::SetSecondary(
166    const std::string& query,
167    const HistoryData::SecondaryDeque& results) {
168  scoped_ptr<base::ListValue> results_list(new base::ListValue);
169  for (size_t i = 0; i< results.size(); ++i)
170    results_list->AppendString(results[i]);
171
172  base::DictionaryValue* entry_dict = GetEntryDict(query);
173  entry_dict->SetWithoutPathExpansion(kKeySecondary, results_list.release());
174  writer_->ScheduleWrite(this);
175}
176
177void HistoryDataStore::SetUpdateTime(const std::string& query,
178                                     const base::Time& update_time) {
179  base::DictionaryValue* entry_dict = GetEntryDict(query);
180  entry_dict->SetWithoutPathExpansion(kKeyUpdateTime,
181                                      new base::StringValue(base::Int64ToString(
182                                          update_time.ToInternalValue())));
183  writer_->ScheduleWrite(this);
184}
185
186void HistoryDataStore::Delete(const std::string& query) {
187  base::DictionaryValue* assoc_dict = GetAssociationDict();
188  assoc_dict->RemoveWithoutPathExpansion(query, NULL);
189  writer_->ScheduleWrite(this);
190}
191
192base::DictionaryValue* HistoryDataStore::GetAssociationDict() {
193  base::DictionaryValue* assoc_dict = NULL;
194  CHECK(cached_json_->GetDictionary(kKeyAssociations, &assoc_dict) &&
195        assoc_dict);
196
197  return assoc_dict;
198}
199
200base::DictionaryValue* HistoryDataStore::GetEntryDict(
201    const std::string& query) {
202  base::DictionaryValue* assoc_dict = GetAssociationDict();
203
204  base::DictionaryValue* entry_dict = NULL;
205  if (!assoc_dict->GetDictionaryWithoutPathExpansion(query, &entry_dict)) {
206    // Creates one if none exists. Ownership is taken in the set call after.
207    entry_dict = new base::DictionaryValue;
208    assoc_dict->SetWithoutPathExpansion(query, entry_dict);
209  }
210
211  return entry_dict;
212}
213
214scoped_ptr<HistoryData::Associations> HistoryDataStore::LoadOnBlockingPool() {
215  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
216
217  int error_code = JSONFileValueSerializer::JSON_NO_ERROR;
218  std::string error_message;
219  JSONFileValueSerializer serializer(data_file_);
220  base::Value* value = serializer.Deserialize(&error_code, &error_message);
221  base::DictionaryValue* dict_value = NULL;
222  if (error_code != JSONFileValueSerializer::JSON_NO_ERROR ||
223      !value ||
224      !value->GetAsDictionary(&dict_value) ||
225      !dict_value) {
226    return scoped_ptr<HistoryData::Associations>();
227  }
228
229  cached_json_.reset(dict_value);
230  return Parse(*dict_value).Pass();
231}
232
233bool HistoryDataStore::SerializeData(std::string* data) {
234  JSONStringValueSerializer serializer(data);
235  serializer.set_pretty_print(true);
236  return serializer.Serialize(*cached_json_.get());
237}
238
239}  // namespace app_list
240