// 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 #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 contacts_to_save, scoped_ptr contact_ids_to_delete, scoped_ptr 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 > contacts(new ScopedVector); scoped_ptr metadata(new UpdateMetadata); // Extract pointers before we calling Pass() so we can use them below. ScopedVector* 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 > contacts, scoped_ptr 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 contacts_to_save, scoped_ptr contact_ids_to_delete, scoped_ptr 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 keys_to_delete; if (is_full_update) { leveldb::ReadOptions options; scoped_ptr 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::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* contacts, UpdateMetadata* metadata) { DCHECK(IsRunByTaskRunner()); DCHECK(success); DCHECK(contacts); DCHECK(metadata); *success = false; contacts->clear(); metadata->Clear(); leveldb::ReadOptions options; scoped_ptr 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(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