| // Copyright (c) 2012 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/chromeos/contacts/contact_database.h" |
| |
| #include <set> |
| |
| #include "base/file_util.h" |
| #include "base/metrics/histogram.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "chrome/browser/chromeos/contacts/contact.pb.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "third_party/leveldatabase/src/include/leveldb/db.h" |
| #include "third_party/leveldatabase/src/include/leveldb/iterator.h" |
| #include "third_party/leveldatabase/src/include/leveldb/options.h" |
| #include "third_party/leveldatabase/src/include/leveldb/slice.h" |
| #include "third_party/leveldatabase/src/include/leveldb/status.h" |
| #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" |
| |
| using content::BrowserThread; |
| |
| namespace contacts { |
| |
| namespace { |
| |
| // Initialization results reported via the "Contacts.DatabaseInitResult" |
| // histogram. |
| enum HistogramInitResult { |
| HISTOGRAM_INIT_RESULT_SUCCESS = 0, |
| HISTOGRAM_INIT_RESULT_FAILURE = 1, |
| HISTOGRAM_INIT_RESULT_DELETED_CORRUPTED = 2, |
| HISTOGRAM_INIT_RESULT_MAX_VALUE = 3, |
| }; |
| |
| // Save results reported via the "Contacts.DatabaseSaveResult" histogram. |
| enum HistogramSaveResult { |
| HISTOGRAM_SAVE_RESULT_SUCCESS = 0, |
| HISTOGRAM_SAVE_RESULT_FAILURE = 1, |
| HISTOGRAM_SAVE_RESULT_MAX_VALUE = 2, |
| }; |
| |
| // Load results reported via the "Contacts.DatabaseLoadResult" histogram. |
| enum HistogramLoadResult { |
| HISTOGRAM_LOAD_RESULT_SUCCESS = 0, |
| HISTOGRAM_LOAD_RESULT_METADATA_PARSE_FAILURE = 1, |
| HISTOGRAM_LOAD_RESULT_CONTACT_PARSE_FAILURE = 2, |
| HISTOGRAM_LOAD_RESULT_MAX_VALUE = 3, |
| }; |
| |
| // LevelDB key used for storing UpdateMetadata messages. |
| const char kUpdateMetadataKey[] = "__chrome_update_metadata__"; |
| |
| } // namespace |
| |
| ContactDatabase::ContactDatabase() |
| : weak_ptr_factory_(this) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); |
| task_runner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); |
| } |
| |
| void ContactDatabase::DestroyOnUIThread() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| task_runner_->PostNonNestableTask( |
| FROM_HERE, |
| base::Bind(&ContactDatabase::DestroyFromTaskRunner, |
| base::Unretained(this))); |
| } |
| |
| void ContactDatabase::Init(const base::FilePath& database_dir, |
| InitCallback callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| bool* success = new bool(false); |
| task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::Bind(&ContactDatabase::InitFromTaskRunner, |
| base::Unretained(this), |
| database_dir, |
| success), |
| base::Bind(&ContactDatabase::RunInitCallback, |
| weak_ptr_factory_.GetWeakPtr(), |
| callback, |
| base::Owned(success))); |
| } |
| |
| void ContactDatabase::SaveContacts(scoped_ptr<ContactPointers> contacts_to_save, |
| scoped_ptr<ContactIds> contact_ids_to_delete, |
| scoped_ptr<UpdateMetadata> metadata, |
| bool is_full_update, |
| SaveCallback callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| bool* success = new bool(false); |
| task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::Bind(&ContactDatabase::SaveContactsFromTaskRunner, |
| base::Unretained(this), |
| base::Passed(&contacts_to_save), |
| base::Passed(&contact_ids_to_delete), |
| base::Passed(&metadata), |
| is_full_update, |
| success), |
| base::Bind(&ContactDatabase::RunSaveCallback, |
| weak_ptr_factory_.GetWeakPtr(), |
| callback, |
| base::Owned(success))); |
| } |
| |
| void ContactDatabase::LoadContacts(LoadCallback callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| bool* success = new bool(false); |
| scoped_ptr<ScopedVector<Contact> > contacts(new ScopedVector<Contact>); |
| scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata); |
| |
| // Extract pointers before we calling Pass() so we can use them below. |
| ScopedVector<Contact>* contacts_ptr = contacts.get(); |
| UpdateMetadata* metadata_ptr = metadata.get(); |
| |
| task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::Bind(&ContactDatabase::LoadContactsFromTaskRunner, |
| base::Unretained(this), |
| success, |
| contacts_ptr, |
| metadata_ptr), |
| base::Bind(&ContactDatabase::RunLoadCallback, |
| weak_ptr_factory_.GetWeakPtr(), |
| callback, |
| base::Owned(success), |
| base::Passed(&contacts), |
| base::Passed(&metadata))); |
| } |
| |
| ContactDatabase::~ContactDatabase() { |
| DCHECK(IsRunByTaskRunner()); |
| } |
| |
| bool ContactDatabase::IsRunByTaskRunner() const { |
| return BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread(); |
| } |
| |
| void ContactDatabase::DestroyFromTaskRunner() { |
| DCHECK(IsRunByTaskRunner()); |
| delete this; |
| } |
| |
| void ContactDatabase::RunInitCallback(InitCallback callback, |
| const bool* success) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| callback.Run(*success); |
| } |
| |
| void ContactDatabase::RunSaveCallback(SaveCallback callback, |
| const bool* success) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| callback.Run(*success); |
| } |
| |
| void ContactDatabase::RunLoadCallback( |
| LoadCallback callback, |
| const bool* success, |
| scoped_ptr<ScopedVector<Contact> > contacts, |
| scoped_ptr<UpdateMetadata> metadata) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| callback.Run(*success, contacts.Pass(), metadata.Pass()); |
| } |
| |
| void ContactDatabase::InitFromTaskRunner(const base::FilePath& database_dir, |
| bool* success) { |
| DCHECK(IsRunByTaskRunner()); |
| DCHECK(success); |
| |
| VLOG(1) << "Opening " << database_dir.value(); |
| UMA_HISTOGRAM_MEMORY_KB("Contacts.DatabaseSizeBytes", |
| base::ComputeDirectorySize(database_dir)); |
| *success = false; |
| HistogramInitResult histogram_result = HISTOGRAM_INIT_RESULT_SUCCESS; |
| |
| leveldb::Options options; |
| options.create_if_missing = true; |
| options.max_open_files = 0; // Use minimum. |
| bool delete_and_retry_on_corruption = true; |
| |
| while (true) { |
| leveldb::DB* db = NULL; |
| leveldb::Status status = |
| leveldb::DB::Open(options, database_dir.value(), &db); |
| if (status.ok()) { |
| CHECK(db); |
| db_.reset(db); |
| *success = true; |
| return; |
| } |
| |
| LOG(WARNING) << "Unable to open " << database_dir.value() << ": " |
| << status.ToString(); |
| |
| // Delete the existing database and try again (just once, though). |
| if (status.IsCorruption() && delete_and_retry_on_corruption) { |
| LOG(WARNING) << "Deleting possibly-corrupt database"; |
| base::DeleteFile(database_dir, true); |
| delete_and_retry_on_corruption = false; |
| histogram_result = HISTOGRAM_INIT_RESULT_DELETED_CORRUPTED; |
| } else { |
| histogram_result = HISTOGRAM_INIT_RESULT_FAILURE; |
| break; |
| } |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseInitResult", |
| histogram_result, |
| HISTOGRAM_INIT_RESULT_MAX_VALUE); |
| } |
| |
| void ContactDatabase::SaveContactsFromTaskRunner( |
| scoped_ptr<ContactPointers> contacts_to_save, |
| scoped_ptr<ContactIds> contact_ids_to_delete, |
| scoped_ptr<UpdateMetadata> metadata, |
| bool is_full_update, |
| bool* success) { |
| DCHECK(IsRunByTaskRunner()); |
| DCHECK(success); |
| VLOG(1) << "Saving " << contacts_to_save->size() << " contact(s) to database " |
| << "and deleting " << contact_ids_to_delete->size() << " as " |
| << (is_full_update ? "full" : "incremental") << " update"; |
| |
| *success = false; |
| |
| // If we're doing a full update, find all of the existing keys first so we can |
| // delete ones that aren't present in the new set of contacts. |
| std::set<std::string> keys_to_delete; |
| if (is_full_update) { |
| leveldb::ReadOptions options; |
| scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options)); |
| db_iterator->SeekToFirst(); |
| while (db_iterator->Valid()) { |
| std::string key = db_iterator->key().ToString(); |
| if (key != kUpdateMetadataKey) |
| keys_to_delete.insert(key); |
| db_iterator->Next(); |
| } |
| } else { |
| for (ContactIds::const_iterator it = contact_ids_to_delete->begin(); |
| it != contact_ids_to_delete->end(); ++it) { |
| keys_to_delete.insert(*it); |
| } |
| } |
| |
| // TODO(derat): Serializing all of the contacts and so we can write them in a |
| // single batch may be expensive, memory-wise. Consider writing them in |
| // several batches instead. (To avoid using partial writes in the event of a |
| // crash, maybe add a dummy "write completed" contact that's removed in the |
| // first batch and added in the last.) |
| leveldb::WriteBatch updates; |
| for (ContactPointers::const_iterator it = contacts_to_save->begin(); |
| it != contacts_to_save->end(); ++it) { |
| const contacts::Contact& contact = **it; |
| if (contact.contact_id() == kUpdateMetadataKey) { |
| LOG(WARNING) << "Skipping contact with reserved ID " |
| << contact.contact_id(); |
| continue; |
| } |
| updates.Put(leveldb::Slice(contact.contact_id()), |
| leveldb::Slice(contact.SerializeAsString())); |
| if (is_full_update) |
| keys_to_delete.erase(contact.contact_id()); |
| } |
| |
| for (std::set<std::string>::const_iterator it = keys_to_delete.begin(); |
| it != keys_to_delete.end(); ++it) { |
| updates.Delete(leveldb::Slice(*it)); |
| } |
| |
| updates.Put(leveldb::Slice(kUpdateMetadataKey), |
| leveldb::Slice(metadata->SerializeAsString())); |
| |
| leveldb::WriteOptions options; |
| options.sync = true; |
| leveldb::Status status = db_->Write(options, &updates); |
| if (status.ok()) |
| *success = true; |
| else |
| LOG(WARNING) << "Failed writing contacts: " << status.ToString(); |
| |
| UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseSaveResult", |
| *success ? |
| HISTOGRAM_SAVE_RESULT_SUCCESS : |
| HISTOGRAM_SAVE_RESULT_FAILURE, |
| HISTOGRAM_SAVE_RESULT_MAX_VALUE); |
| } |
| |
| void ContactDatabase::LoadContactsFromTaskRunner( |
| bool* success, |
| ScopedVector<Contact>* contacts, |
| UpdateMetadata* metadata) { |
| DCHECK(IsRunByTaskRunner()); |
| DCHECK(success); |
| DCHECK(contacts); |
| DCHECK(metadata); |
| |
| *success = false; |
| contacts->clear(); |
| metadata->Clear(); |
| |
| leveldb::ReadOptions options; |
| scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options)); |
| db_iterator->SeekToFirst(); |
| while (db_iterator->Valid()) { |
| leveldb::Slice value_slice = db_iterator->value(); |
| |
| if (db_iterator->key().ToString() == kUpdateMetadataKey) { |
| if (!metadata->ParseFromArray(value_slice.data(), value_slice.size())) { |
| LOG(WARNING) << "Unable to parse metadata"; |
| UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult", |
| HISTOGRAM_LOAD_RESULT_METADATA_PARSE_FAILURE, |
| HISTOGRAM_LOAD_RESULT_MAX_VALUE); |
| return; |
| } |
| } else { |
| scoped_ptr<Contact> contact(new Contact); |
| if (!contact->ParseFromArray(value_slice.data(), value_slice.size())) { |
| LOG(WARNING) << "Unable to parse contact " |
| << db_iterator->key().ToString(); |
| UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult", |
| HISTOGRAM_LOAD_RESULT_CONTACT_PARSE_FAILURE, |
| HISTOGRAM_LOAD_RESULT_MAX_VALUE); |
| return; |
| } |
| contacts->push_back(contact.release()); |
| } |
| db_iterator->Next(); |
| } |
| |
| *success = true; |
| UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult", |
| HISTOGRAM_LOAD_RESULT_SUCCESS, |
| HISTOGRAM_LOAD_RESULT_MAX_VALUE); |
| } |
| |
| } // namespace contacts |