Chromium Code Reviews| Index: chrome/browser/ui/app_list/search/history_data_store.cc |
| diff --git a/chrome/browser/ui/app_list/search/history_data_store.cc b/chrome/browser/ui/app_list/search/history_data_store.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..d204627caeed65bfb12fc933274f8aefdff7b1ec |
| --- /dev/null |
| +++ b/chrome/browser/ui/app_list/search/history_data_store.cc |
| @@ -0,0 +1,236 @@ |
| +// Copyright 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/ui/app_list/search/history_data_store.h" |
| + |
| +#include "base/callback.h" |
| +#include "base/json/json_file_value_serializer.h" |
| +#include "base/json/json_string_value_serializer.h" |
| +#include "base/logging.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/task_runner_util.h" |
| +#include "base/threading/sequenced_worker_pool.h" |
| +#include "base/values.h" |
| +#include "content/public/browser/browser_thread.h" |
| + |
| +using content::BrowserThread; |
| + |
| +namespace app_list { |
| + |
| +namespace { |
| + |
| +const char kKeyVersion[] = "version"; |
|
James Cook
2013/05/23 21:51:08
I'm glad you're including a file format version nu
xiyuan
2013/05/23 22:38:13
Future is always hard to predict. :p
|
| +const char kCurrentVersion[] = "1"; |
| + |
| +const char kKeyAssociations[] = "associations"; |
| +const char kKeyPrimary[] = "p"; |
| +const char kKeySecondary[] = "s"; |
| +const char kKeyUpdateTime[] = "t"; |
| + |
| +// Extracts strings from ListValue. |
| +void GetSecondary(const base::ListValue* list, |
| + HistoryData::SecondaryDeque* secondary) { |
| + HistoryData::SecondaryDeque results; |
| + for (base::ListValue::const_iterator it = list->begin(); |
| + it != list->end(); ++it) { |
| + std::string str; |
| + if (!(*it)->GetAsString(&str)) |
| + return; |
| + |
| + results.push_back(str); |
| + } |
| + |
| + secondary->swap(results); |
| +} |
| + |
| +// V1 format json dictionary: |
| +// { |
| +// "version": "1", |
| +// "associations": { |
| +// "user typed query": { |
| +// "p" : "result id of primary association", |
| +// "s" : [ |
| +// "result id of 1st (oldest) secondary association", |
| +// ... |
| +// "result id of the latest secondary association" |
|
James Cook
2013/05/23 21:51:08
nit: I would say "newest" instead of "latest" beca
xiyuan
2013/05/23 22:38:13
Done.
|
| +// ], |
| +// "t" : "last_update_timestamp" |
| +// }, |
| +// ... |
| +// } |
| +// } |
| +scoped_ptr<HistoryData::Associations> Parse(const base::DictionaryValue& dict) { |
| + std::string version; |
| + if (!dict.GetStringWithoutPathExpansion(kKeyVersion, &version) || |
| + version != kCurrentVersion) { |
| + return scoped_ptr<HistoryData::Associations>(); |
| + } |
| + |
| + const base::DictionaryValue* assoc_dict = NULL; |
| + if (!dict.GetDictionaryWithoutPathExpansion(kKeyAssociations, &assoc_dict) || |
| + !assoc_dict) { |
| + return scoped_ptr<HistoryData::Associations>(); |
| + } |
| + |
| + scoped_ptr<HistoryData::Associations> data(new HistoryData::Associations); |
| + for (base::DictionaryValue::Iterator it(*assoc_dict); |
| + !it.IsAtEnd(); it.Advance()) { |
| + const base::DictionaryValue* entry_dict = NULL; |
| + if (!it.value().GetAsDictionary(&entry_dict)) |
| + continue; |
| + |
| + std::string primary; |
| + std::string update_time_string; |
| + if (!entry_dict->GetStringWithoutPathExpansion(kKeyPrimary, &primary) || |
| + !entry_dict->GetStringWithoutPathExpansion(kKeyUpdateTime, |
| + &update_time_string)) { |
| + continue; |
| + } |
| + |
| + const base::ListValue* secondary_list = NULL; |
| + HistoryData::SecondaryDeque secondary; |
| + if (entry_dict->GetListWithoutPathExpansion(kKeySecondary, &secondary_list)) |
| + GetSecondary(secondary_list, &secondary); |
| + |
| + const std::string& query = it.key(); |
| + HistoryData::Data& association_data = (*data.get())[query]; |
| + association_data.primary = primary; |
| + association_data.secondary.swap(secondary); |
| + |
| + int64 update_time_val; |
| + base::StringToInt64(update_time_string, &update_time_val); |
| + association_data.update_time = |
| + base::Time::FromInternalValue(update_time_val); |
| + } |
| + |
| + return data.Pass(); |
| +} |
| + |
| +// An empty callback used to ensure file tasks are cleared. |
| +void EmptyCallback() {} |
| + |
| +} // namespace |
| + |
| +HistoryDataStore::HistoryDataStore(const base::FilePath& data_file) |
| + : data_file_(data_file) { |
| + std::string token("app-launcher-history-data-store"); |
| + token.append(data_file.AsUTF8Unsafe()); |
| + |
| + base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); |
| + file_task_runner_ = |
| + pool->GetSequencedTaskRunner(pool->GetNamedSequenceToken(token)); |
| + writer_.reset(new base::ImportantFileWriter(data_file, file_task_runner_)); |
| + |
| + cached_json_.reset(new base::DictionaryValue); |
| + cached_json_->SetString(kKeyVersion, kCurrentVersion); |
| + cached_json_->Set(kKeyAssociations, new base::DictionaryValue); |
| +} |
| + |
| +HistoryDataStore::~HistoryDataStore() { |
| + Flush(OnFlushedCallback()); |
|
James Cook
2013/05/23 21:51:08
Will this force this data store to be written on s
xiyuan
2013/05/23 22:38:13
Made |file_task_runner_| be a SKIP_ON_SHUTDOWN tas
|
| +} |
| + |
| +void HistoryDataStore::Flush(const OnFlushedCallback& on_flushed) { |
| + if (writer_->HasPendingWrite()) |
| + writer_->DoScheduledWrite(); |
| + |
| + if (on_flushed.is_null()) |
| + return; |
| + |
| + file_task_runner_->PostTaskAndReply( |
| + FROM_HERE, base::Bind(&EmptyCallback), on_flushed); |
| +} |
| + |
| +void HistoryDataStore::Load( |
| + const HistoryDataStore::OnLoadedCallback& on_loaded) { |
| + base::PostTaskAndReplyWithResult( |
| + file_task_runner_, |
| + FROM_HERE, |
| + base::Bind(&HistoryDataStore::LoadOnBlockingPool, this), |
| + on_loaded); |
| +} |
| + |
| +void HistoryDataStore::SetPrimary(const std::string& query, |
| + const std::string& result) { |
| + base::DictionaryValue* entry_dict = GetEntryDict(query); |
| + entry_dict->SetWithoutPathExpansion(kKeyPrimary, |
| + new base::StringValue(result)); |
| + writer_->ScheduleWrite(this); |
| +} |
| + |
| +void HistoryDataStore::SetSecondary( |
| + const std::string& query, |
| + const HistoryData::SecondaryDeque& results) { |
| + scoped_ptr<base::ListValue> results_list(new base::ListValue); |
| + for (size_t i = 0; i< results.size(); ++i) |
| + results_list->AppendString(results[i]); |
| + |
| + base::DictionaryValue* entry_dict = GetEntryDict(query); |
| + entry_dict->SetWithoutPathExpansion(kKeySecondary, results_list.release()); |
| + writer_->ScheduleWrite(this); |
| +} |
| + |
| +void HistoryDataStore::SetUpdateTime(const std::string& query, |
| + const base::Time& update_time) { |
| + base::DictionaryValue* entry_dict = GetEntryDict(query); |
| + entry_dict->SetWithoutPathExpansion(kKeyUpdateTime, |
| + new base::StringValue(base::Int64ToString( |
| + update_time.ToInternalValue()))); |
| + writer_->ScheduleWrite(this); |
| +} |
| + |
| +void HistoryDataStore::Delete(const std::string& query) { |
| + base::DictionaryValue* assoc_dict = GetAssociationDict(); |
| + assoc_dict->RemoveWithoutPathExpansion(query, NULL); |
| + writer_->ScheduleWrite(this); |
| +} |
| + |
| +base::DictionaryValue* HistoryDataStore::GetAssociationDict() { |
| + base::DictionaryValue* assoc_dict = NULL; |
| + CHECK(cached_json_->GetDictionary(kKeyAssociations, &assoc_dict) && |
| + assoc_dict); |
| + |
| + return assoc_dict; |
| +} |
| + |
| +base::DictionaryValue* HistoryDataStore::GetEntryDict( |
| + const std::string& query) { |
| + base::DictionaryValue* assoc_dict = GetAssociationDict(); |
| + |
| + base::DictionaryValue* entry_dict = NULL; |
| + if (!assoc_dict->GetDictionaryWithoutPathExpansion(query, &entry_dict)) { |
| + // Creates one if none exists. Ownership is taken in the set call after. |
| + entry_dict = new base::DictionaryValue; |
| + assoc_dict->SetWithoutPathExpansion(query, entry_dict); |
| + } |
| + |
| + return entry_dict; |
| +} |
| + |
| +scoped_ptr<HistoryData::Associations> HistoryDataStore::LoadOnBlockingPool() { |
| + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
|
James Cook
2013/05/23 21:51:08
I like thread assertions.
xiyuan
2013/05/23 22:38:13
Me too.
|
| + |
| + int error_code = JSONFileValueSerializer::JSON_NO_ERROR; |
| + std::string error_message; |
| + JSONFileValueSerializer serializer(data_file_); |
| + base::Value* value = serializer.Deserialize(&error_code, &error_message); |
| + base::DictionaryValue* dict_value = NULL; |
| + if (error_code != JSONFileValueSerializer::JSON_NO_ERROR || |
| + !value || |
| + !value->GetAsDictionary(&dict_value) || |
| + !dict_value) { |
| + return scoped_ptr<HistoryData::Associations>(); |
| + } |
| + |
| + cached_json_.reset(dict_value); |
| + return Parse(*dict_value).Pass(); |
| +} |
| + |
| +bool HistoryDataStore::SerializeData(std::string* data) { |
| + JSONStringValueSerializer serializer(data); |
| + serializer.set_pretty_print(true); |
| + return serializer.Serialize(*cached_json_.get()); |
| +} |
| + |
| +} // namespace app_list |