普通文本  |  344行  |  12 KB

// 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