1 // Copyright (c) 2012 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.
5 #include "chrome/browser/chromeos/contacts/contact_database.h"
9 #include "base/file_util.h"
10 #include "base/metrics/histogram.h"
11 #include "base/sequenced_task_runner.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "chrome/browser/chromeos/contacts/contact.pb.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "leveldb/db.h"
16 #include "leveldb/iterator.h"
17 #include "leveldb/options.h"
18 #include "leveldb/slice.h"
19 #include "leveldb/status.h"
20 #include "leveldb/write_batch.h"
22 using content::BrowserThread
;
28 // Initialization results reported via the "Contacts.DatabaseInitResult"
30 enum HistogramInitResult
{
31 HISTOGRAM_INIT_RESULT_SUCCESS
= 0,
32 HISTOGRAM_INIT_RESULT_FAILURE
= 1,
33 HISTOGRAM_INIT_RESULT_DELETED_CORRUPTED
= 2,
34 HISTOGRAM_INIT_RESULT_MAX_VALUE
= 3,
37 // Save results reported via the "Contacts.DatabaseSaveResult" histogram.
38 enum HistogramSaveResult
{
39 HISTOGRAM_SAVE_RESULT_SUCCESS
= 0,
40 HISTOGRAM_SAVE_RESULT_FAILURE
= 1,
41 HISTOGRAM_SAVE_RESULT_MAX_VALUE
= 2,
44 // Load results reported via the "Contacts.DatabaseLoadResult" histogram.
45 enum HistogramLoadResult
{
46 HISTOGRAM_LOAD_RESULT_SUCCESS
= 0,
47 HISTOGRAM_LOAD_RESULT_METADATA_PARSE_FAILURE
= 1,
48 HISTOGRAM_LOAD_RESULT_CONTACT_PARSE_FAILURE
= 2,
49 HISTOGRAM_LOAD_RESULT_MAX_VALUE
= 3,
52 // LevelDB key used for storing UpdateMetadata messages.
53 const char kUpdateMetadataKey
[] = "__chrome_update_metadata__";
57 ContactDatabase::ContactDatabase()
58 : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
59 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
60 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
61 task_runner_
= pool
->GetSequencedTaskRunner(pool
->GetSequenceToken());
64 void ContactDatabase::DestroyOnUIThread() {
65 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
66 weak_ptr_factory_
.InvalidateWeakPtrs();
67 task_runner_
->PostNonNestableTask(
69 base::Bind(&ContactDatabase::DestroyFromTaskRunner
,
70 base::Unretained(this)));
73 void ContactDatabase::Init(const FilePath
& database_dir
,
74 InitCallback callback
) {
75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
76 bool* success
= new bool(false);
77 task_runner_
->PostTaskAndReply(
79 base::Bind(&ContactDatabase::InitFromTaskRunner
,
80 base::Unretained(this),
83 base::Bind(&ContactDatabase::RunInitCallback
,
84 weak_ptr_factory_
.GetWeakPtr(),
86 base::Owned(success
)));
89 void ContactDatabase::SaveContacts(scoped_ptr
<ContactPointers
> contacts_to_save
,
90 scoped_ptr
<ContactIds
> contact_ids_to_delete
,
91 scoped_ptr
<UpdateMetadata
> metadata
,
93 SaveCallback callback
) {
94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
95 bool* success
= new bool(false);
96 task_runner_
->PostTaskAndReply(
98 base::Bind(&ContactDatabase::SaveContactsFromTaskRunner
,
99 base::Unretained(this),
100 base::Passed(contacts_to_save
.Pass()),
101 base::Passed(contact_ids_to_delete
.Pass()),
102 base::Passed(metadata
.Pass()),
105 base::Bind(&ContactDatabase::RunSaveCallback
,
106 weak_ptr_factory_
.GetWeakPtr(),
108 base::Owned(success
)));
111 void ContactDatabase::LoadContacts(LoadCallback callback
) {
112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
114 bool* success
= new bool(false);
115 scoped_ptr
<ScopedVector
<Contact
> > contacts(new ScopedVector
<Contact
>);
116 scoped_ptr
<UpdateMetadata
> metadata(new UpdateMetadata
);
118 // Extract pointers before we calling Pass() so we can use them below.
119 ScopedVector
<Contact
>* contacts_ptr
= contacts
.get();
120 UpdateMetadata
* metadata_ptr
= metadata
.get();
122 task_runner_
->PostTaskAndReply(
124 base::Bind(&ContactDatabase::LoadContactsFromTaskRunner
,
125 base::Unretained(this),
129 base::Bind(&ContactDatabase::RunLoadCallback
,
130 weak_ptr_factory_
.GetWeakPtr(),
132 base::Owned(success
),
133 base::Passed(contacts
.Pass()),
134 base::Passed(metadata
.Pass())));
137 ContactDatabase::~ContactDatabase() {
138 DCHECK(IsRunByTaskRunner());
141 bool ContactDatabase::IsRunByTaskRunner() const {
142 return BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread();
145 void ContactDatabase::DestroyFromTaskRunner() {
146 DCHECK(IsRunByTaskRunner());
150 void ContactDatabase::RunInitCallback(InitCallback callback
,
151 const bool* success
) {
152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
153 callback
.Run(*success
);
156 void ContactDatabase::RunSaveCallback(SaveCallback callback
,
157 const bool* success
) {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
159 callback
.Run(*success
);
162 void ContactDatabase::RunLoadCallback(
163 LoadCallback callback
,
165 scoped_ptr
<ScopedVector
<Contact
> > contacts
,
166 scoped_ptr
<UpdateMetadata
> metadata
) {
167 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
168 callback
.Run(*success
, contacts
.Pass(), metadata
.Pass());
171 void ContactDatabase::InitFromTaskRunner(const FilePath
& database_dir
,
173 DCHECK(IsRunByTaskRunner());
176 VLOG(1) << "Opening " << database_dir
.value();
177 UMA_HISTOGRAM_MEMORY_KB("Contacts.DatabaseSizeBytes",
178 file_util::ComputeDirectorySize(database_dir
));
180 HistogramInitResult histogram_result
= HISTOGRAM_INIT_RESULT_SUCCESS
;
182 leveldb::Options options
;
183 options
.create_if_missing
= true;
184 bool delete_and_retry_on_corruption
= true;
187 leveldb::DB
* db
= NULL
;
188 leveldb::Status status
=
189 leveldb::DB::Open(options
, database_dir
.value(), &db
);
197 LOG(WARNING
) << "Unable to open " << database_dir
.value() << ": "
198 << status
.ToString();
200 // Delete the existing database and try again (just once, though).
201 if (status
.IsCorruption() && delete_and_retry_on_corruption
) {
202 LOG(WARNING
) << "Deleting possibly-corrupt database";
203 file_util::Delete(database_dir
, true);
204 delete_and_retry_on_corruption
= false;
205 histogram_result
= HISTOGRAM_INIT_RESULT_DELETED_CORRUPTED
;
207 histogram_result
= HISTOGRAM_INIT_RESULT_FAILURE
;
212 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseInitResult",
214 HISTOGRAM_INIT_RESULT_MAX_VALUE
);
217 void ContactDatabase::SaveContactsFromTaskRunner(
218 scoped_ptr
<ContactPointers
> contacts_to_save
,
219 scoped_ptr
<ContactIds
> contact_ids_to_delete
,
220 scoped_ptr
<UpdateMetadata
> metadata
,
223 DCHECK(IsRunByTaskRunner());
225 VLOG(1) << "Saving " << contacts_to_save
->size() << " contact(s) to database "
226 << "and deleting " << contact_ids_to_delete
->size() << " as "
227 << (is_full_update
? "full" : "incremental") << " update";
231 // If we're doing a full update, find all of the existing keys first so we can
232 // delete ones that aren't present in the new set of contacts.
233 std::set
<std::string
> keys_to_delete
;
234 if (is_full_update
) {
235 leveldb::ReadOptions options
;
236 scoped_ptr
<leveldb::Iterator
> db_iterator(db_
->NewIterator(options
));
237 db_iterator
->SeekToFirst();
238 while (db_iterator
->Valid()) {
239 std::string key
= db_iterator
->key().ToString();
240 if (key
!= kUpdateMetadataKey
)
241 keys_to_delete
.insert(key
);
245 for (ContactIds::const_iterator it
= contact_ids_to_delete
->begin();
246 it
!= contact_ids_to_delete
->end(); ++it
) {
247 keys_to_delete
.insert(*it
);
251 // TODO(derat): Serializing all of the contacts and so we can write them in a
252 // single batch may be expensive, memory-wise. Consider writing them in
253 // several batches instead. (To avoid using partial writes in the event of a
254 // crash, maybe add a dummy "write completed" contact that's removed in the
255 // first batch and added in the last.)
256 leveldb::WriteBatch updates
;
257 for (ContactPointers::const_iterator it
= contacts_to_save
->begin();
258 it
!= contacts_to_save
->end(); ++it
) {
259 const contacts::Contact
& contact
= **it
;
260 if (contact
.contact_id() == kUpdateMetadataKey
) {
261 LOG(WARNING
) << "Skipping contact with reserved ID "
262 << contact
.contact_id();
265 updates
.Put(leveldb::Slice(contact
.contact_id()),
266 leveldb::Slice(contact
.SerializeAsString()));
268 keys_to_delete
.erase(contact
.contact_id());
271 for (std::set
<std::string
>::const_iterator it
= keys_to_delete
.begin();
272 it
!= keys_to_delete
.end(); ++it
) {
273 updates
.Delete(leveldb::Slice(*it
));
276 updates
.Put(leveldb::Slice(kUpdateMetadataKey
),
277 leveldb::Slice(metadata
->SerializeAsString()));
279 leveldb::WriteOptions options
;
281 leveldb::Status status
= db_
->Write(options
, &updates
);
285 LOG(WARNING
) << "Failed writing contacts: " << status
.ToString();
287 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseSaveResult",
289 HISTOGRAM_SAVE_RESULT_SUCCESS
:
290 HISTOGRAM_SAVE_RESULT_FAILURE
,
291 HISTOGRAM_SAVE_RESULT_MAX_VALUE
);
294 void ContactDatabase::LoadContactsFromTaskRunner(
296 ScopedVector
<Contact
>* contacts
,
297 UpdateMetadata
* metadata
) {
298 DCHECK(IsRunByTaskRunner());
307 leveldb::ReadOptions options
;
308 scoped_ptr
<leveldb::Iterator
> db_iterator(db_
->NewIterator(options
));
309 db_iterator
->SeekToFirst();
310 while (db_iterator
->Valid()) {
311 leveldb::Slice value_slice
= db_iterator
->value();
313 if (db_iterator
->key().ToString() == kUpdateMetadataKey
) {
314 if (!metadata
->ParseFromArray(value_slice
.data(), value_slice
.size())) {
315 LOG(WARNING
) << "Unable to parse metadata";
316 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult",
317 HISTOGRAM_LOAD_RESULT_METADATA_PARSE_FAILURE
,
318 HISTOGRAM_LOAD_RESULT_MAX_VALUE
);
322 scoped_ptr
<Contact
> contact(new Contact
);
323 if (!contact
->ParseFromArray(value_slice
.data(), value_slice
.size())) {
324 LOG(WARNING
) << "Unable to parse contact "
325 << db_iterator
->key().ToString();
326 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult",
327 HISTOGRAM_LOAD_RESULT_CONTACT_PARSE_FAILURE
,
328 HISTOGRAM_LOAD_RESULT_MAX_VALUE
);
331 contacts
->push_back(contact
.release());
337 UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult",
338 HISTOGRAM_LOAD_RESULT_SUCCESS
,
339 HISTOGRAM_LOAD_RESULT_MAX_VALUE
);
342 } // namespace contacts