Chromium Code Reviews| Index: chrome/browser/chromeos/contacts/contact_database.cc |
| diff --git a/chrome/browser/chromeos/contacts/contact_database.cc b/chrome/browser/chromeos/contacts/contact_database.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f4b4d80bc014d46a9cb8ec8ff6d2862f89f148b6 |
| --- /dev/null |
| +++ b/chrome/browser/chromeos/contacts/contact_database.cc |
| @@ -0,0 +1,199 @@ |
| +// 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 "chrome/browser/chromeos/contacts/contact.pb.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "leveldb/db.h" |
| +#include "leveldb/iterator.h" |
| +#include "leveldb/options.h" |
| +#include "leveldb/slice.h" |
| +#include "leveldb/status.h" |
| +#include "leveldb/write_batch.h" |
| + |
| +using content::BrowserThread; |
| + |
| +namespace contacts { |
| + |
| +ContactDatabase::ContactDatabase() |
| + : condition_variable_(&lock_), |
| + num_outstanding_file_operations_(0) { |
| +} |
| + |
| +ContactDatabase::~ContactDatabase() { |
| + base::AutoLock auto_lock(lock_); |
| + while (num_outstanding_file_operations_ > 0) |
| + condition_variable_.Wait(); |
|
satorux1
2012/07/30 17:15:07
I'd suggest not to do this. In gdata_cache.cc we s
Daniel Erat
2012/07/30 22:12:09
Thanks, I didn't know about this, but it's very us
|
| +} |
| + |
| +void ContactDatabase::Init(const FilePath& database_dir, |
| + InitCallback callback) { |
| + IncrementOutstandingFileOperations(); |
| + BrowserThread::PostTask( |
| + BrowserThread::FILE, FROM_HERE, |
|
satorux1
2012/07/30 17:15:07
FILE thread is supposed to be dead. Please use Seq
Daniel Erat
2012/07/30 22:12:09
Done.
|
| + base::Bind(&ContactDatabase::InitOnFileThread, AsWeakPtr(), |
| + database_dir, callback)); |
| +} |
| + |
| +void ContactDatabase::SaveContacts(scoped_ptr<ContactPointers> contacts, |
| + bool is_full_update, |
| + SaveCallback callback) { |
|
satorux1
2012/07/30 17:15:07
You might want to add something like:
DCHECK(Curr
Daniel Erat
2012/07/30 22:12:09
Done.
|
| + IncrementOutstandingFileOperations(); |
| + BrowserThread::PostTask( |
| + BrowserThread::FILE, FROM_HERE, |
| + base::Bind(&ContactDatabase::SaveContactsOnFileThread, AsWeakPtr(), |
| + base::Passed(contacts.Pass()), is_full_update, callback)); |
| +} |
| + |
| +void ContactDatabase::LoadContacts(LoadCallback callback) { |
| + IncrementOutstandingFileOperations(); |
| + BrowserThread::PostTask( |
| + BrowserThread::FILE, FROM_HERE, |
| + base::Bind(&ContactDatabase::LoadContactsOnFileThread, AsWeakPtr(), |
| + callback)); |
| +} |
| + |
| +void ContactDatabase::InitOnFileThread(const FilePath& database_dir, |
| + InitCallback callback) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + bool success = InitInternal(database_dir); |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, FROM_HERE, base::Bind(callback, success)); |
| + DecrementOutstandingFileOperations(); |
| +} |
| + |
| +bool ContactDatabase::InitInternal(const FilePath& database_dir) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + VLOG(1) << "Opening " << database_dir.value(); |
| + |
| + leveldb::Options options; |
| + options.create_if_missing = true; |
| + 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); |
| + return true; |
| + } |
| + |
| + LOG(WARNING) << "Unable to open " << database_dir.value() << ": " |
| + << status.ToString(); |
| + |
| + // Delete the existing database and try again (just once, though). |
|
satorux1
2012/07/30 17:15:07
oh this is a nice technique!
|
| + if (status.IsCorruption() && delete_and_retry_on_corruption) { |
| + LOG(WARNING) << "Deleting possibly-corrupt database"; |
| + file_util::Delete(database_dir, true); |
| + delete_and_retry_on_corruption = false; |
| + } else { |
| + break; |
| + } |
| + } |
| + |
| + return false; |
| +} |
| + |
| +void ContactDatabase::SaveContactsOnFileThread( |
| + scoped_ptr<ContactPointers> contacts, |
| + bool is_full_update, |
| + SaveCallback callback) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + VLOG(1) << "Saving " << contacts->size() << " contact(s) to database as " |
| + << (is_full_update ? "full" : "partial") << " update"; |
| + |
| + // 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()) { |
| + keys_to_delete.insert(db_iterator->key().ToString()); |
| + db_iterator->Next(); |
| + } |
| + } |
| + |
| + // 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->begin(); |
| + it != contacts->end(); ++it) { |
| + const contacts::Contact& contact = **it; |
| + updates.Put(leveldb::Slice(contact.provider_id()), |
| + leveldb::Slice(contact.SerializeAsString())); |
| + keys_to_delete.erase(contact.provider_id()); |
| + } |
| + |
| + for (std::set<std::string>::const_iterator it = keys_to_delete.begin(); |
| + it != keys_to_delete.end(); ++it) { |
| + updates.Delete(leveldb::Slice(*it)); |
| + } |
| + |
| + leveldb::WriteOptions options; |
| + options.sync = true; |
| + leveldb::Status status = db_->Write(options, &updates); |
| + if (!status.ok()) |
| + LOG(WARNING) << "Failed writing contacts: " << status.ToString(); |
| + |
| + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(callback, status.ok())); |
| + DecrementOutstandingFileOperations(); |
| +} |
| + |
| +void ContactDatabase::LoadContactsOnFileThread(LoadCallback callback) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + scoped_ptr<ScopedVector<Contact> > contacts(new ScopedVector<Contact>()); |
| + bool success = LoadContactsInternal(contacts.get()); |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, FROM_HERE, |
| + base::Bind(callback, success, base::Passed(contacts.Pass()))); |
| + DecrementOutstandingFileOperations(); |
| +} |
| + |
| +bool ContactDatabase::LoadContactsInternal(ScopedVector<Contact>* contacts) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + DCHECK(contacts); |
| + contacts->clear(); |
| + |
| + leveldb::ReadOptions options; |
| + scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options)); |
| + db_iterator->SeekToFirst(); |
| + while (db_iterator->Valid()) { |
| + scoped_ptr<Contact> contact(new Contact); |
| + leveldb::Slice value_slice = db_iterator->value(); |
| + if (!contact->ParseFromArray(value_slice.data(), value_slice.size())) { |
| + LOG(WARNING) << "Unable to parse contact " |
| + << db_iterator->key().ToString(); |
| + return false; |
| + } |
| + contacts->push_back(contact.release()); |
| + db_iterator->Next(); |
| + } |
| + return true; |
| +} |
| + |
| +void ContactDatabase::IncrementOutstandingFileOperations() { |
| + base::AutoLock auto_lock(lock_); |
| + num_outstanding_file_operations_++; |
| +} |
| + |
| +void ContactDatabase::DecrementOutstandingFileOperations() { |
| + base::AutoLock auto_lock(lock_); |
| + DCHECK_GT(num_outstanding_file_operations_, 0); |
| + num_outstanding_file_operations_--; |
| + condition_variable_.Signal(); |
| +} |
| + |
| +} // namespace contacts |