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