Home | History | Annotate | Download | only in contacts
      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.
      4 
      5 #include "chrome/browser/chromeos/contacts/contact_database.h"
      6 
      7 #include <set>
      8 
      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 "third_party/leveldatabase/src/include/leveldb/db.h"
     16 #include "third_party/leveldatabase/src/include/leveldb/iterator.h"
     17 #include "third_party/leveldatabase/src/include/leveldb/options.h"
     18 #include "third_party/leveldatabase/src/include/leveldb/slice.h"
     19 #include "third_party/leveldatabase/src/include/leveldb/status.h"
     20 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
     21 
     22 using content::BrowserThread;
     23 
     24 namespace contacts {
     25 
     26 namespace {
     27 
     28 // Initialization results reported via the "Contacts.DatabaseInitResult"
     29 // histogram.
     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,
     35 };
     36 
     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,
     42 };
     43 
     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,
     50 };
     51 
     52 // LevelDB key used for storing UpdateMetadata messages.
     53 const char kUpdateMetadataKey[] = "__chrome_update_metadata__";
     54 
     55 }  // namespace
     56 
     57 ContactDatabase::ContactDatabase()
     58     : weak_ptr_factory_(this) {
     59   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     60   base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
     61   task_runner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken());
     62 }
     63 
     64 void ContactDatabase::DestroyOnUIThread() {
     65   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     66   weak_ptr_factory_.InvalidateWeakPtrs();
     67   task_runner_->PostNonNestableTask(
     68       FROM_HERE,
     69       base::Bind(&ContactDatabase::DestroyFromTaskRunner,
     70                  base::Unretained(this)));
     71 }
     72 
     73 void ContactDatabase::Init(const base::FilePath& database_dir,
     74                            InitCallback callback) {
     75   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     76   bool* success = new bool(false);
     77   task_runner_->PostTaskAndReply(
     78       FROM_HERE,
     79       base::Bind(&ContactDatabase::InitFromTaskRunner,
     80                  base::Unretained(this),
     81                  database_dir,
     82                  success),
     83       base::Bind(&ContactDatabase::RunInitCallback,
     84                  weak_ptr_factory_.GetWeakPtr(),
     85                  callback,
     86                  base::Owned(success)));
     87 }
     88 
     89 void ContactDatabase::SaveContacts(scoped_ptr<ContactPointers> contacts_to_save,
     90                                    scoped_ptr<ContactIds> contact_ids_to_delete,
     91                                    scoped_ptr<UpdateMetadata> metadata,
     92                                    bool is_full_update,
     93                                    SaveCallback callback) {
     94   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     95   bool* success = new bool(false);
     96   task_runner_->PostTaskAndReply(
     97       FROM_HERE,
     98       base::Bind(&ContactDatabase::SaveContactsFromTaskRunner,
     99                  base::Unretained(this),
    100                  base::Passed(&contacts_to_save),
    101                  base::Passed(&contact_ids_to_delete),
    102                  base::Passed(&metadata),
    103                  is_full_update,
    104                  success),
    105       base::Bind(&ContactDatabase::RunSaveCallback,
    106                  weak_ptr_factory_.GetWeakPtr(),
    107                  callback,
    108                  base::Owned(success)));
    109 }
    110 
    111 void ContactDatabase::LoadContacts(LoadCallback callback) {
    112   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    113 
    114   bool* success = new bool(false);
    115   scoped_ptr<ScopedVector<Contact> > contacts(new ScopedVector<Contact>);
    116   scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata);
    117 
    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();
    121 
    122   task_runner_->PostTaskAndReply(
    123       FROM_HERE,
    124       base::Bind(&ContactDatabase::LoadContactsFromTaskRunner,
    125                  base::Unretained(this),
    126                  success,
    127                  contacts_ptr,
    128                  metadata_ptr),
    129       base::Bind(&ContactDatabase::RunLoadCallback,
    130                  weak_ptr_factory_.GetWeakPtr(),
    131                  callback,
    132                  base::Owned(success),
    133                  base::Passed(&contacts),
    134                  base::Passed(&metadata)));
    135 }
    136 
    137 ContactDatabase::~ContactDatabase() {
    138   DCHECK(IsRunByTaskRunner());
    139 }
    140 
    141 bool ContactDatabase::IsRunByTaskRunner() const {
    142   return BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread();
    143 }
    144 
    145 void ContactDatabase::DestroyFromTaskRunner() {
    146   DCHECK(IsRunByTaskRunner());
    147   delete this;
    148 }
    149 
    150 void ContactDatabase::RunInitCallback(InitCallback callback,
    151                                       const bool* success) {
    152   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    153   callback.Run(*success);
    154 }
    155 
    156 void ContactDatabase::RunSaveCallback(SaveCallback callback,
    157                                       const bool* success) {
    158   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    159   callback.Run(*success);
    160 }
    161 
    162 void ContactDatabase::RunLoadCallback(
    163     LoadCallback callback,
    164     const bool* success,
    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());
    169 }
    170 
    171 void ContactDatabase::InitFromTaskRunner(const base::FilePath& database_dir,
    172                                          bool* success) {
    173   DCHECK(IsRunByTaskRunner());
    174   DCHECK(success);
    175 
    176   VLOG(1) << "Opening " << database_dir.value();
    177   UMA_HISTOGRAM_MEMORY_KB("Contacts.DatabaseSizeBytes",
    178                           base::ComputeDirectorySize(database_dir));
    179   *success = false;
    180   HistogramInitResult histogram_result = HISTOGRAM_INIT_RESULT_SUCCESS;
    181 
    182   leveldb::Options options;
    183   options.create_if_missing = true;
    184   options.max_open_files = 64;  // Use minimum.
    185   bool delete_and_retry_on_corruption = true;
    186 
    187   while (true) {
    188     leveldb::DB* db = NULL;
    189     leveldb::Status status =
    190         leveldb::DB::Open(options, database_dir.value(), &db);
    191     if (status.ok()) {
    192       CHECK(db);
    193       db_.reset(db);
    194       *success = true;
    195       return;
    196     }
    197 
    198     LOG(WARNING) << "Unable to open " << database_dir.value() << ": "
    199                  << status.ToString();
    200 
    201     // Delete the existing database and try again (just once, though).
    202     if (status.IsCorruption() && delete_and_retry_on_corruption) {
    203       LOG(WARNING) << "Deleting possibly-corrupt database";
    204       base::DeleteFile(database_dir, true);
    205       delete_and_retry_on_corruption = false;
    206       histogram_result = HISTOGRAM_INIT_RESULT_DELETED_CORRUPTED;
    207     } else {
    208       histogram_result = HISTOGRAM_INIT_RESULT_FAILURE;
    209       break;
    210     }
    211   }
    212 
    213   UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseInitResult",
    214                             histogram_result,
    215                             HISTOGRAM_INIT_RESULT_MAX_VALUE);
    216 }
    217 
    218 void ContactDatabase::SaveContactsFromTaskRunner(
    219     scoped_ptr<ContactPointers> contacts_to_save,
    220     scoped_ptr<ContactIds> contact_ids_to_delete,
    221     scoped_ptr<UpdateMetadata> metadata,
    222     bool is_full_update,
    223     bool* success) {
    224   DCHECK(IsRunByTaskRunner());
    225   DCHECK(success);
    226   VLOG(1) << "Saving " << contacts_to_save->size() << " contact(s) to database "
    227           << "and deleting " << contact_ids_to_delete->size() << " as "
    228           << (is_full_update ? "full" : "incremental") << " update";
    229 
    230   *success = false;
    231 
    232   // If we're doing a full update, find all of the existing keys first so we can
    233   // delete ones that aren't present in the new set of contacts.
    234   std::set<std::string> keys_to_delete;
    235   if (is_full_update) {
    236     leveldb::ReadOptions options;
    237     scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options));
    238     db_iterator->SeekToFirst();
    239     while (db_iterator->Valid()) {
    240       std::string key = db_iterator->key().ToString();
    241       if (key != kUpdateMetadataKey)
    242         keys_to_delete.insert(key);
    243       db_iterator->Next();
    244     }
    245   } else {
    246     for (ContactIds::const_iterator it = contact_ids_to_delete->begin();
    247          it != contact_ids_to_delete->end(); ++it) {
    248       keys_to_delete.insert(*it);
    249     }
    250   }
    251 
    252   // TODO(derat): Serializing all of the contacts and so we can write them in a
    253   // single batch may be expensive, memory-wise.  Consider writing them in
    254   // several batches instead.  (To avoid using partial writes in the event of a
    255   // crash, maybe add a dummy "write completed" contact that's removed in the
    256   // first batch and added in the last.)
    257   leveldb::WriteBatch updates;
    258   for (ContactPointers::const_iterator it = contacts_to_save->begin();
    259        it != contacts_to_save->end(); ++it) {
    260     const contacts::Contact& contact = **it;
    261     if (contact.contact_id() == kUpdateMetadataKey) {
    262       LOG(WARNING) << "Skipping contact with reserved ID "
    263                    << contact.contact_id();
    264       continue;
    265     }
    266     updates.Put(leveldb::Slice(contact.contact_id()),
    267                 leveldb::Slice(contact.SerializeAsString()));
    268     if (is_full_update)
    269       keys_to_delete.erase(contact.contact_id());
    270   }
    271 
    272   for (std::set<std::string>::const_iterator it = keys_to_delete.begin();
    273        it != keys_to_delete.end(); ++it) {
    274     updates.Delete(leveldb::Slice(*it));
    275   }
    276 
    277   updates.Put(leveldb::Slice(kUpdateMetadataKey),
    278               leveldb::Slice(metadata->SerializeAsString()));
    279 
    280   leveldb::WriteOptions options;
    281   options.sync = true;
    282   leveldb::Status status = db_->Write(options, &updates);
    283   if (status.ok())
    284     *success = true;
    285   else
    286     LOG(WARNING) << "Failed writing contacts: " << status.ToString();
    287 
    288   UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseSaveResult",
    289                             *success ?
    290                             HISTOGRAM_SAVE_RESULT_SUCCESS :
    291                             HISTOGRAM_SAVE_RESULT_FAILURE,
    292                             HISTOGRAM_SAVE_RESULT_MAX_VALUE);
    293 }
    294 
    295 void ContactDatabase::LoadContactsFromTaskRunner(
    296     bool* success,
    297     ScopedVector<Contact>* contacts,
    298     UpdateMetadata* metadata) {
    299   DCHECK(IsRunByTaskRunner());
    300   DCHECK(success);
    301   DCHECK(contacts);
    302   DCHECK(metadata);
    303 
    304   *success = false;
    305   contacts->clear();
    306   metadata->Clear();
    307 
    308   leveldb::ReadOptions options;
    309   scoped_ptr<leveldb::Iterator> db_iterator(db_->NewIterator(options));
    310   db_iterator->SeekToFirst();
    311   while (db_iterator->Valid()) {
    312     leveldb::Slice value_slice = db_iterator->value();
    313 
    314     if (db_iterator->key().ToString() == kUpdateMetadataKey) {
    315       if (!metadata->ParseFromArray(value_slice.data(), value_slice.size())) {
    316         LOG(WARNING) << "Unable to parse metadata";
    317         UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult",
    318                                   HISTOGRAM_LOAD_RESULT_METADATA_PARSE_FAILURE,
    319                                   HISTOGRAM_LOAD_RESULT_MAX_VALUE);
    320         return;
    321       }
    322     } else {
    323       scoped_ptr<Contact> contact(new Contact);
    324       if (!contact->ParseFromArray(value_slice.data(), value_slice.size())) {
    325         LOG(WARNING) << "Unable to parse contact "
    326                      << db_iterator->key().ToString();
    327         UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult",
    328                                   HISTOGRAM_LOAD_RESULT_CONTACT_PARSE_FAILURE,
    329                                   HISTOGRAM_LOAD_RESULT_MAX_VALUE);
    330         return;
    331       }
    332       contacts->push_back(contact.release());
    333     }
    334     db_iterator->Next();
    335   }
    336 
    337   *success = true;
    338   UMA_HISTOGRAM_ENUMERATION("Contacts.DatabaseLoadResult",
    339                             HISTOGRAM_LOAD_RESULT_SUCCESS,
    340                             HISTOGRAM_LOAD_RESULT_MAX_VALUE);
    341 }
    342 
    343 }  // namespace contacts
    344