Commit da326fb6 authored by derat@chromium.org's avatar derat@chromium.org

contacts: Add GoogleContactStore.

This class uses GDataContactsService to download contacts
from Google and uses ContactDatabase to save them to disk.
It also contains logic for periodically refreshing contacts.

BUG=128805
TEST=none
TBR=ben@chromium.org

Review URL: https://chromiumcodereview.appspot.com/10829144

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149805 0039d316-1c4b-4281-b951-d872f2087c98
parent 74bb5598
...@@ -29,7 +29,8 @@ const char kUpdateMetadataKey[] = "__chrome_update_metadata__"; ...@@ -29,7 +29,8 @@ const char kUpdateMetadataKey[] = "__chrome_update_metadata__";
} // namespace } // namespace
ContactDatabase::ContactDatabase() : weak_ptr_factory_(this) { ContactDatabase::ContactDatabase()
: ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool();
task_runner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken()); task_runner_ = pool->GetSequencedTaskRunner(pool->GetSequenceToken());
......
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_CONTACTS_CONTACT_STORE_H_
#define CHROME_BROWSER_CHROMEOS_CONTACTS_CONTACT_STORE_H_
#include <string>
#include <vector>
#include "base/basictypes.h"
namespace contacts {
class Contact;
typedef std::vector<const Contact*> ContactPointers;
class ContactStoreObserver;
// Interface for classes that store contacts from a particular source.
class ContactStore {
public:
ContactStore() {}
virtual ~ContactStore() {}
// Appends all (non-deleted) contacts to |contacts_out|.
virtual void AppendContacts(ContactPointers* contacts_out) = 0;
// Returns the contact identified by |provider_id|.
// NULL is returned if the contact doesn't exist.
virtual const Contact* GetContactByProviderId(
const std::string& provider_id) = 0;
// Adds or removes an observer.
virtual void AddObserver(ContactStoreObserver* observer) = 0;
virtual void RemoveObserver(ContactStoreObserver* observer) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(ContactStore);
};
} // namespace contacts
#endif // CHROME_BROWSER_CHROMEOS_CONTACTS_CONTACT_STORE_H_
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_CONTACTS_CONTACT_STORE_OBSERVER_H_
#define CHROME_BROWSER_CHROMEOS_CONTACTS_CONTACT_STORE_OBSERVER_H_
#include "base/basictypes.h"
namespace contacts {
class ContactStore;
// Interface for observing changes to a ContactStore.
class ContactStoreObserver {
public:
ContactStoreObserver() {}
virtual ~ContactStoreObserver() {}
// Called when the contacts stored within a ContactStore are updated.
virtual void OnContactsUpdated(ContactStore* store) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(ContactStoreObserver);
};
} // namespace contacts
#endif // CHROME_BROWSER_CHROMEOS_CONTACTS_CONTACT_STORE_OBSERVER_H_
...@@ -138,11 +138,8 @@ void CopyContacts(const ContactPointers& source, ...@@ -138,11 +138,8 @@ void CopyContacts(const ContactPointers& source,
ScopedVector<Contact>* dest) { ScopedVector<Contact>* dest) {
DCHECK(dest); DCHECK(dest);
dest->clear(); dest->clear();
for (size_t i = 0; i < source.size(); ++i) { for (size_t i = 0; i < source.size(); ++i)
Contact* contact = new Contact; dest->push_back(new Contact(*source[i]));
*contact = *source[i];
dest->push_back(contact);
}
} }
void CopyContacts(const ScopedVector<Contact>& source, void CopyContacts(const ScopedVector<Contact>& source,
......
// 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/google_contact_store.h"
#include <algorithm>
#include "base/bind.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "chrome/browser/chromeos/contacts/contact.pb.h"
#include "chrome/browser/chromeos/contacts/contact_database.h"
#include "chrome/browser/chromeos/contacts/contact_store_observer.h"
#include "chrome/browser/chromeos/gdata/gdata_contacts_service.h"
#include "chrome/browser/chromeos/gdata/gdata_system_service.h"
#include "chrome/browser/chromeos/gdata/gdata_util.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace contacts {
namespace {
// Name of the directory within the profile directory where the contact database
// is stored.
const FilePath::CharType kDatabaseDirectoryName[] =
FILE_PATH_LITERAL("Google Contacts");
// We wait this long after the last update has completed successfully before
// updating again.
// TODO(derat): Decide what this should be.
const int kUpdateIntervalSec = 600;
// https://developers.google.com/google-apps/contacts/v3/index says that deleted
// contact (groups?) will only be returned for 30 days after deletion when the
// "showdeleted" parameter is set. If it's been longer than that since the last
// successful update, we do a full refresh to make sure that we haven't missed
// any deletions. Use 29 instead to make sure that we don't run afoul of
// daylight saving time shenanigans or minor skew in the system clock.
const int kForceFullUpdateDays = 29;
// When an update fails, we initially wait this many seconds before retrying.
// The delay increases exponentially in response to repeated failures.
const int kUpdateFailureInitialRetrySec = 5;
// Amount by which |update_delay_on_next_failure_| is multiplied on failure.
const int kUpdateFailureBackoffFactor = 2;
} // namespace
GoogleContactStore::TestAPI::TestAPI(GoogleContactStore* store)
: store_(store) {
DCHECK(store);
}
GoogleContactStore::TestAPI::~TestAPI() {
store_ = NULL;
}
void GoogleContactStore::TestAPI::SetDatabase(ContactDatabaseInterface* db) {
store_->DestroyDatabase();
store_->db_ = db;
}
void GoogleContactStore::TestAPI::SetGDataService(
gdata::GDataContactsServiceInterface* service) {
store_->gdata_service_for_testing_.reset(service);
}
void GoogleContactStore::TestAPI::DoUpdate() {
store_->UpdateContacts();
}
GoogleContactStore::GoogleContactStore(Profile* profile)
: profile_(profile),
contacts_deleter_(&contacts_),
db_(new ContactDatabase),
update_delay_on_next_failure_(
base::TimeDelta::FromSeconds(kUpdateFailureInitialRetrySec)),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
GoogleContactStore::~GoogleContactStore() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
weak_ptr_factory_.InvalidateWeakPtrs();
DestroyDatabase();
}
void GoogleContactStore::Init() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
FilePath db_path = profile_->GetPath().Append(kDatabaseDirectoryName);
VLOG(1) << "Initializing contact database \"" << db_path.value() << "\" for "
<< profile_->GetProfileName();
db_->Init(db_path,
base::Bind(&GoogleContactStore::OnDatabaseInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
void GoogleContactStore::AppendContacts(ContactPointers* contacts_out) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(contacts_out);
for (ContactMap::const_iterator it = contacts_.begin();
it != contacts_.end(); ++it) {
if (!it->second->deleted())
contacts_out->push_back(it->second);
}
}
const Contact* GoogleContactStore::GetContactByProviderId(
const std::string& provider_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ContactMap::const_iterator it = contacts_.find(provider_id);
return (it != contacts_.end() && !it->second->deleted()) ? it->second : NULL;
}
void GoogleContactStore::AddObserver(ContactStoreObserver* observer) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(observer);
observers_.AddObserver(observer);
}
void GoogleContactStore::RemoveObserver(ContactStoreObserver* observer) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(observer);
observers_.RemoveObserver(observer);
}
base::Time GoogleContactStore::GetCurrentTime() const {
return !current_time_for_testing_.is_null() ?
current_time_for_testing_ :
base::Time::Now();
}
void GoogleContactStore::DestroyDatabase() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (db_) {
db_->DestroyOnUIThread();
db_ = NULL;
}
}
void GoogleContactStore::UpdateContacts() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::Time min_update_time;
base::TimeDelta time_since_last_update =
last_successful_update_start_time_.is_null() ?
base::TimeDelta() :
GetCurrentTime() - last_successful_update_start_time_;
if (!last_contact_update_time_.is_null() &&
time_since_last_update <
base::TimeDelta::FromDays(kForceFullUpdateDays)) {
// TODO(derat): I'm adding one millisecond to the last update time here as I
// don't want to re-download the same most-recently-updated contact each
// time, but what happens if within the same millisecond, contact A is
// updated, we do a sync, and then contact B is updated? I'm probably being
// overly paranoid about this.
min_update_time =
last_contact_update_time_ + base::TimeDelta::FromMilliseconds(1);
}
if (min_update_time.is_null()) {
VLOG(1) << "Downloading all contacts for " << profile_->GetProfileName();
} else {
VLOG(1) << "Downloading contacts updated since "
<< gdata::util::FormatTimeAsString(min_update_time) << " for "
<< profile_->GetProfileName();
}
gdata::GDataContactsServiceInterface* service =
gdata_service_for_testing_.get() ?
gdata_service_for_testing_.get() :
gdata::GDataSystemServiceFactory::GetForProfile(profile_)->
contacts_service();
DCHECK(service);
service->DownloadContacts(
base::Bind(&GoogleContactStore::OnDownloadSuccess,
weak_ptr_factory_.GetWeakPtr(),
min_update_time.is_null(),
GetCurrentTime()),
base::Bind(&GoogleContactStore::OnDownloadFailure,
weak_ptr_factory_.GetWeakPtr()),
min_update_time);
}
void GoogleContactStore::ScheduleUpdate(bool last_update_was_successful) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::TimeDelta delay;
if (last_update_was_successful) {
delay = base::TimeDelta::FromSeconds(kUpdateIntervalSec);
update_delay_on_next_failure_ =
base::TimeDelta::FromSeconds(kUpdateFailureInitialRetrySec);
} else {
delay = update_delay_on_next_failure_;
update_delay_on_next_failure_ = std::min(
update_delay_on_next_failure_ * kUpdateFailureBackoffFactor,
base::TimeDelta::FromSeconds(kUpdateIntervalSec));
}
VLOG(1) << "Scheduling update of " << profile_->GetProfileName()
<< " in " << delay.InSeconds() << " second(s)";
update_timer_.Start(
FROM_HERE, delay, this, &GoogleContactStore::UpdateContacts);
}
void GoogleContactStore::MergeContacts(
bool is_full_update,
scoped_ptr<ScopedVector<Contact> > updated_contacts) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (is_full_update) {
STLDeleteValues(&contacts_);
contacts_.clear();
}
for (ScopedVector<Contact>::iterator it = updated_contacts->begin();
it != updated_contacts->end(); ++it) {
Contact* contact = *it;
VLOG(1) << "Updating " << contact->provider_id();
ContactMap::iterator map_it = contacts_.find(contact->provider_id());
if (map_it == contacts_.end()) {
contacts_[contact->provider_id()] = contact;
} else {
delete map_it->second;
map_it->second = contact;
}
}
// Make sure that the Contact objects won't be destroyed when
// |updated_contacts| is destroyed.
size_t num_updated_contacts = updated_contacts->size();
updated_contacts->weak_clear();
if (is_full_update || num_updated_contacts > 0) {
// Find the latest update time.
last_contact_update_time_ = base::Time();
for (ContactMap::const_iterator it = contacts_.begin();
it != contacts_.end(); ++it) {
const Contact* contact = it->second;
base::Time update_time =
base::Time::FromInternalValue(contact->update_time());
if (!update_time.is_null() &&
(last_contact_update_time_.is_null() ||
last_contact_update_time_ < update_time)) {
last_contact_update_time_ = update_time;
}
}
}
VLOG(1) << "Last contact update time is "
<< (last_contact_update_time_.is_null() ?
std::string("null") :
gdata::util::FormatTimeAsString(last_contact_update_time_));
}
void GoogleContactStore::OnDownloadSuccess(
bool is_full_update,
const base::Time& update_start_time,
scoped_ptr<ScopedVector<Contact> > updated_contacts) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(1) << "Got " << updated_contacts->size() << " contact(s) for "
<< profile_->GetProfileName();
size_t num_updated_contacts = updated_contacts->size();
MergeContacts(is_full_update, updated_contacts.Pass());
last_successful_update_start_time_ = update_start_time;
if (is_full_update || num_updated_contacts > 0) {
FOR_EACH_OBSERVER(ContactStoreObserver,
observers_,
OnContactsUpdated(this));
}
if (db_) {
scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
for (ContactMap::const_iterator it = contacts_.begin();
it != contacts_.end(); ++it) {
contacts_to_save->push_back(it->second);
}
scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata);
metadata->set_last_update_start_time(update_start_time.ToInternalValue());
db_->SaveContacts(
contacts_to_save.Pass(),
metadata.Pass(),
is_full_update,
base::Bind(&GoogleContactStore::OnDatabaseContactsSaved,
weak_ptr_factory_.GetWeakPtr()));
// We'll schedule an update from OnDatabaseContactsSaved() after we're done
// writing to the database -- we don't want to modify the contacts while
// they're being used by the database.
} else {
ScheduleUpdate(true);
}
}
void GoogleContactStore::OnDownloadFailure() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
LOG(WARNING) << "Contacts download failed for " << profile_->GetProfileName();
ScheduleUpdate(false);
}
void GoogleContactStore::OnDatabaseInitialized(bool success) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (success) {
VLOG(1) << "Contact database initialized for "
<< profile_->GetProfileName();
db_->LoadContacts(base::Bind(&GoogleContactStore::OnDatabaseContactsLoaded,
weak_ptr_factory_.GetWeakPtr()));
} else {
LOG(WARNING) << "Failed to initialize contact database for "
<< profile_->GetProfileName();
// Limp along as best as we can: throw away the database and do an update,
// which will schedule further updates.
DestroyDatabase();
UpdateContacts();
}
}
void GoogleContactStore::OnDatabaseContactsLoaded(
bool success,
scoped_ptr<ScopedVector<Contact> > contacts,
scoped_ptr<UpdateMetadata> metadata) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (success) {
VLOG(1) << "Loaded " << contacts->size() << " contact(s) from database";
MergeContacts(true, contacts.Pass());
last_successful_update_start_time_ =
base::Time::FromInternalValue(metadata->last_update_start_time());
if (!contacts_.empty()) {
FOR_EACH_OBSERVER(ContactStoreObserver,
observers_,
OnContactsUpdated(this));
}
} else {
LOG(WARNING) << "Failed to load contacts from database";
}
UpdateContacts();
}
void GoogleContactStore::OnDatabaseContactsSaved(bool success) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!success)
LOG(WARNING) << "Failed to save contacts to database";
// We only update the database when we've successfully downloaded contacts, so
// report success to ScheduleUpdate() even if the database update failed.
ScheduleUpdate(true);
}
} // namespace contacts
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_CONTACTS_GOOGLE_CONTACT_STORE_H_
#define CHROME_BROWSER_CHROMEOS_CONTACTS_GOOGLE_CONTACT_STORE_H_
#include "chrome/browser/chromeos/contacts/contact_store.h"
#include <map>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/stl_util.h"
#include "base/time.h"
#include "base/timer.h"
class Profile;
namespace gdata {
class GDataContactsServiceInterface;
}
namespace contacts {
class Contact;
class ContactDatabaseInterface;
class UpdateMetadata;
// A collection of contacts from a Google account.
class GoogleContactStore : public ContactStore {
public:
class TestAPI {
public:
explicit TestAPI(GoogleContactStore* store);
~TestAPI();
bool update_scheduled() { return store_->update_timer_.IsRunning(); }
void set_current_time(const base::Time& time) {
store_->current_time_for_testing_ = time;
}
// Takes ownership of |db|. Must be called before Init().
void SetDatabase(ContactDatabaseInterface* db);
// Takes ownership of |service|. Must be called before Init().
void SetGDataService(gdata::GDataContactsServiceInterface* service);
// Triggers an update, similar to what happens when the update timer fires.
void DoUpdate();
private:
GoogleContactStore* store_; // not owned
DISALLOW_COPY_AND_ASSIGN(TestAPI);
};
explicit GoogleContactStore(Profile* profile);
virtual ~GoogleContactStore();
void Init();
// ContactStore implementation:
virtual void AppendContacts(ContactPointers* contacts_out) OVERRIDE;
virtual const Contact* GetContactByProviderId(
const std::string& provider_id) OVERRIDE;
virtual void AddObserver(ContactStoreObserver* observer) OVERRIDE;
virtual void RemoveObserver(ContactStoreObserver* observer) OVERRIDE;
private:
// Map from a contact's Google-assigned ID to the contact itself.
typedef std::map<std::string, Contact*> ContactMap;
// Returns the current time. Uses |current_time_for_testing_| instead if it's
// set.
base::Time GetCurrentTime() const;
// Destroys |db_| if non-NULL and resets the pointer.
// The underlying data is preserved on-disk.
void DestroyDatabase();
// Asynchronously downloads updated contacts and merges them into |contacts_|.
void UpdateContacts();
// Starts |update_timer_| so UpdateContacts() will be run.
void ScheduleUpdate(bool last_update_was_successful);
// Moves |updated_contacts| into |contacts_| and updates
// |last_contact_update_time_|.
void MergeContacts(bool is_full_update,
scoped_ptr<ScopedVector<Contact> > updated_contacts);
// Handles a successful download, merging |updated_contacts| and saving the
// updated contacts to |db_|.
void OnDownloadSuccess(bool is_full_update,
const base::Time& update_start_time,
scoped_ptr<ScopedVector<Contact> > updated_contacts);
// Handles a failed update. A new update is scheduled.
void OnDownloadFailure();
// Handles |db_|'s initialization. On success, we start loading the contacts
// from the database; otherwise, we throw out the database and schedule an
// update.
void OnDatabaseInitialized(bool success);
// Handles contacts being loaded from |db_|. On success, we merge the loaded
// contacts. No matter what, we call UpdateContacts().
void OnDatabaseContactsLoaded(bool success,
scoped_ptr<ScopedVector<Contact> > contacts,
scoped_ptr<UpdateMetadata> metadata);
// Handles contacts being saved to |db_|. Now that the contacts are no longer
// being accessed by the database, we schedule an update.
void OnDatabaseContactsSaved(bool success);
Profile* profile_; // not owned
ObserverList<ContactStoreObserver> observers_;
// Owns the pointed-to Contact values.
ContactMap contacts_;
// Deletes values in |contacts_|.
STLValueDeleter<ContactMap> contacts_deleter_;
// Most-recent time that an entry in |contacts_| has been updated (as reported
// by Google).
base::Time last_contact_update_time_;
// Used to save contacts to disk and load them at startup. Owns the object.
ContactDatabaseInterface* db_;
// If non-NULL, used in place of the real GData service to download contacts.
scoped_ptr<gdata::GDataContactsServiceInterface> gdata_service_for_testing_;
// Used to schedule calls to UpdateContacts().
base::OneShotTimer<GoogleContactStore> update_timer_;
// Time at which the last successful update was started.
base::Time last_successful_update_start_time_;
// Amount of time that we'll wait before retrying the next time that an update
// fails.
base::TimeDelta update_delay_on_next_failure_;
// If non-null, used in place of base::Time::Now() when the current time is
// needed.
base::Time current_time_for_testing_;
// Note: This should remain the last member so it'll be destroyed and
// invalidate its weak pointers before any other members are destroyed.
base::WeakPtrFactory<GoogleContactStore> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(GoogleContactStore);
};
} // namespace contacts
#endif // CHROME_BROWSER_CHROMEOS_CONTACTS_GOOGLE_CONTACT_STORE_H_
// 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/google_contact_store.h"
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/time.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/browser/chromeos/contacts/contact.pb.h"
#include "chrome/browser/chromeos/contacts/contact_store_observer.h"
#include "chrome/browser/chromeos/contacts/contact_test_util.h"
#include "chrome/browser/chromeos/contacts/fake_contact_database.h"
#include "chrome/browser/chromeos/gdata/gdata_contacts_service_stub.h"
#include "chrome/browser/chromeos/gdata/gdata_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
using content::BrowserThread;
namespace contacts {
namespace test {
// ContactStoreObserver implementation that just counts the number of times
// that it's been told that a store has been updated.
class TestContactStoreObserver : public ContactStoreObserver {
public:
TestContactStoreObserver() : num_updates_(0) {}
~TestContactStoreObserver() {}
int num_updates() const { return num_updates_; }
void reset_stats() { num_updates_ = 0; }
// ContactStoreObserver overrides:
void OnContactsUpdated(ContactStore* store) OVERRIDE {
DCHECK(store);
num_updates_++;
}
private:
// Number of times that OnContactsUpdated() has been called.
int num_updates_;
DISALLOW_COPY_AND_ASSIGN(TestContactStoreObserver);
};
class GoogleContactStoreTest : public testing::Test {
public:
GoogleContactStoreTest() : ui_thread_(BrowserThread::UI, &message_loop_) {}
virtual ~GoogleContactStoreTest() {}
protected:
// testing::Test implementation.
virtual void SetUp() OVERRIDE {
testing::Test::SetUp();
profile_.reset(new TestingProfile);
store_.reset(new GoogleContactStore(profile_.get()));
store_->AddObserver(&observer_);
test_api_.reset(new GoogleContactStore::TestAPI(store_.get()));
db_ = new FakeContactDatabase;
test_api_->SetDatabase(db_);
gdata_service_ = new gdata::GDataContactsServiceStub;
test_api_->SetGDataService(gdata_service_);
}
MessageLoopForUI message_loop_;
content::TestBrowserThread ui_thread_;
TestContactStoreObserver observer_;
scoped_ptr<TestingProfile> profile_;
scoped_ptr<GoogleContactStore> store_;
scoped_ptr<GoogleContactStore::TestAPI> test_api_;
FakeContactDatabase* db_; // not owned
gdata::GDataContactsServiceStub* gdata_service_; // not owned
private:
DISALLOW_COPY_AND_ASSIGN(GoogleContactStoreTest);
};
TEST_F(GoogleContactStoreTest, LoadFromDatabase) {
// Store two contacts in the database.
const std::string kProviderId1 = "provider1";
const std::string kProviderId2 = "provider2";
scoped_ptr<Contact> contact1(new Contact);
InitContact(kProviderId1, "1", false, contact1.get());
scoped_ptr<Contact> contact2(new Contact);
InitContact(kProviderId2, "2", false, contact2.get());
ContactPointers db_contacts;
db_contacts.push_back(contact1.get());
db_contacts.push_back(contact2.get());
UpdateMetadata db_metadata;
db_->SetContacts(db_contacts, db_metadata);
// Tell the GData service to report failure, initialize the store, and check
// that the contacts from the database are loaded.
gdata_service_->set_download_should_succeed(false);
store_->Init();
ContactPointers loaded_contacts;
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(VarContactsToString(2, contact1.get(), contact2.get()),
ContactsToString(loaded_contacts));
EXPECT_TRUE(test_api_->update_scheduled());
EXPECT_EQ(1, observer_.num_updates());
// Check that we can also grab the contact via its ID.
const Contact* loaded_contact1 = store_->GetContactByProviderId(kProviderId1);
ASSERT_TRUE(loaded_contact1);
EXPECT_EQ(ContactToString(*contact1), ContactToString(*loaded_contact1));
// We should get NULL if we request a nonexistent contact.
EXPECT_FALSE(store_->GetContactByProviderId("bogus_id"));
}
TEST_F(GoogleContactStoreTest, LoadFromGData) {
// Store two contacts in the GData service.
scoped_ptr<Contact> contact1(new Contact);
InitContact("provider1", "1", false, contact1.get());
scoped_ptr<Contact> contact2(new Contact);
InitContact("provider2", "2", false, contact2.get());
ContactPointers gdata_contacts;
gdata_contacts.push_back(contact1.get());
gdata_contacts.push_back(contact2.get());
gdata_service_->SetContacts(gdata_contacts, base::Time());
// Initialize the store and check that the contacts are loaded from GData.
store_->Init();
ContactPointers loaded_contacts;
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(VarContactsToString(2, contact1.get(), contact2.get()),
ContactsToString(loaded_contacts));
EXPECT_TRUE(test_api_->update_scheduled());
EXPECT_EQ(1, observer_.num_updates());
// The contacts should've been saved to the database, too.
EXPECT_EQ(VarContactsToString(2, contact1.get(), contact2.get()),
ContactsToString(db_->contacts()));
}
TEST_F(GoogleContactStoreTest, UpdateFromGData) {
scoped_ptr<Contact> contact1(new Contact);
InitContact("provider1", "1", false, contact1.get());
scoped_ptr<Contact> contact2(new Contact);
InitContact("provider2", "2", false, contact2.get());
scoped_ptr<Contact> contact3(new Contact);
InitContact("provider3", "3", false, contact3.get());
// Store the first two contacts in the database.
ContactPointers db_contacts;
db_contacts.push_back(contact1.get());
db_contacts.push_back(contact2.get());
UpdateMetadata db_metadata;
db_->SetContacts(db_contacts, db_metadata);
// Store all three in the GData service. We expect the update request to ask
// for all contacts updated one millisecond after the newest contact in the
// database.
ContactPointers gdata_contacts;
gdata_contacts.push_back(contact1.get());
gdata_contacts.push_back(contact2.get());
gdata_contacts.push_back(contact3.get());
gdata_service_->SetContacts(
gdata_contacts,
base::Time::FromInternalValue(contact2->update_time()) +
base::TimeDelta::FromMilliseconds(1));
// Check that the store ends up with all three contacts.
store_->Init();
ContactPointers loaded_contacts;
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(VarContactsToString(
3, contact1.get(), contact2.get(), contact3.get()),
ContactsToString(loaded_contacts));
EXPECT_EQ(2, observer_.num_updates());
// All three contacts should've been saved to the database.
EXPECT_EQ(VarContactsToString(
3, contact1.get(), contact2.get(), contact3.get()),
ContactsToString(db_->contacts()));
EXPECT_TRUE(test_api_->update_scheduled());
}
TEST_F(GoogleContactStoreTest, FetchUpdatedContacts) {
scoped_ptr<Contact> contact1(new Contact);
InitContact("provider1", "1", false, contact1.get());
scoped_ptr<Contact> contact2(new Contact);
InitContact("provider2", "2", false, contact2.get());
scoped_ptr<Contact> contact3(new Contact);
InitContact("provider3", "3", false, contact3.get());
// Tell the GData service to return all three contacts in response to a full
// update.
ContactPointers gdata_contacts;
gdata_contacts.push_back(contact1.get());
gdata_contacts.push_back(contact2.get());
gdata_contacts.push_back(contact3.get());
gdata_service_->SetContacts(gdata_contacts, base::Time());
store_->Init();
ContactPointers loaded_contacts;
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(loaded_contacts));
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(db_->contacts()));
EXPECT_TRUE(test_api_->update_scheduled());
EXPECT_EQ(1, observer_.num_updates());
observer_.reset_stats();
// Update the third contact.
contact3->set_full_name("new full name");
base::Time old_contact3_update_time =
base::Time::FromInternalValue(contact3->update_time());
contact3->set_update_time(
(old_contact3_update_time +
base::TimeDelta::FromSeconds(10)).ToInternalValue());
gdata_service_->SetContacts(
gdata_contacts,
old_contact3_update_time + base::TimeDelta::FromMilliseconds(1));
// Check that the updated contact is loaded (i.e. the store passed the
// correct minimum update time to the service).
test_api_->DoUpdate();
loaded_contacts.clear();
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(loaded_contacts));
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(db_->contacts()));
EXPECT_TRUE(test_api_->update_scheduled());
EXPECT_EQ(1, observer_.num_updates());
observer_.reset_stats();
// The next update should be based on the third contact's new update time.
contact3->set_full_name("yet another full name");
gdata_service_->SetContacts(
gdata_contacts,
base::Time::FromInternalValue(contact3->update_time()) +
base::TimeDelta::FromMilliseconds(1));
test_api_->DoUpdate();
loaded_contacts.clear();
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(loaded_contacts));
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(db_->contacts()));
EXPECT_TRUE(test_api_->update_scheduled());
EXPECT_EQ(1, observer_.num_updates());
}
TEST_F(GoogleContactStoreTest, DontReturnDeletedContacts) {
// Tell GData to return a single deleted contact.
const std::string kProviderId = "provider";
scoped_ptr<Contact> contact(new Contact);
InitContact(kProviderId, "1", true, contact.get());
ContactPointers gdata_contacts;
gdata_contacts.push_back(contact.get());
gdata_service_->SetContacts(gdata_contacts, base::Time());
// The contact shouldn't be returned by AppendContacts() or
// GetContactByProviderId().
store_->Init();
ContactPointers loaded_contacts;
store_->AppendContacts(&loaded_contacts);
EXPECT_TRUE(loaded_contacts.empty());
EXPECT_FALSE(store_->GetContactByProviderId(kProviderId));
}
// Test that we do a full refresh from GData if enough time has passed since the
// last refresh that we might've missed some contact deletions (see
// |kForceFullUpdateDays| in google_contact_store.cc).
TEST_F(GoogleContactStoreTest, FullRefreshAfterThirtyDays) {
base::Time::Exploded kInitTimeExploded = { 2012, 3, 0, 1, 16, 34, 56, 123 };
base::Time kInitTime = base::Time::FromUTCExploded(kInitTimeExploded);
base::Time kOldUpdateTime = kInitTime - base::TimeDelta::FromDays(31);
scoped_ptr<Contact> contact1(new Contact);
InitContact("provider1", "1", false, contact1.get());
contact1->set_update_time(kOldUpdateTime.ToInternalValue());
scoped_ptr<Contact> contact2(new Contact);
InitContact("provider2", "2", false, contact2.get());
contact2->set_update_time(kOldUpdateTime.ToInternalValue());
// Put both contacts in the database, along with metadata saying that the last
// successful update was 31 days in the past.
ContactPointers db_contacts;
db_contacts.push_back(contact1.get());
db_contacts.push_back(contact2.get());
UpdateMetadata db_metadata;
db_metadata.set_last_update_start_time(kOldUpdateTime.ToInternalValue());
db_->SetContacts(db_contacts, db_metadata);
// Tell the GData service to return only the first contact and to expect a
// full refresh (since it's been a long time since the last update).
ContactPointers gdata_contacts;
gdata_contacts.push_back(contact1.get());
gdata_service_->SetContacts(gdata_contacts, base::Time());
test_api_->set_current_time(kInitTime);
store_->Init();
ContactPointers loaded_contacts;
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(loaded_contacts));
EXPECT_TRUE(test_api_->update_scheduled());
// Make GData return both contacts now in response to an incremental update.
gdata_contacts.clear();
contact2->set_update_time(kInitTime.ToInternalValue());
gdata_contacts.push_back(contact1.get());
gdata_contacts.push_back(contact2.get());
gdata_service_->SetContacts(
gdata_contacts, kOldUpdateTime + base::TimeDelta::FromMilliseconds(1));
// Advance the time by twenty days and check that the update is successful.
base::Time kFirstUpdateTime = kInitTime + base::TimeDelta::FromDays(20);
test_api_->set_current_time(kFirstUpdateTime);
test_api_->DoUpdate();
loaded_contacts.clear();
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(loaded_contacts));
// After we advance the time by 31 days, we should do a full refresh again.
gdata_contacts.clear();
gdata_contacts.push_back(contact1.get());
gdata_service_->SetContacts(gdata_contacts, base::Time());
base::Time kSecondUpdateTime =
kFirstUpdateTime + base::TimeDelta::FromDays(31);
test_api_->set_current_time(kSecondUpdateTime);
test_api_->DoUpdate();
loaded_contacts.clear();
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(loaded_contacts));
}
TEST_F(GoogleContactStoreTest, HandleDatabaseInitFailure) {
scoped_ptr<Contact> contact1(new Contact);
InitContact("provider1", "1", false, contact1.get());
// Store a contact in the database but make initialization fail.
ContactPointers db_contacts;
db_contacts.push_back(contact1.get());
UpdateMetadata db_metadata;
db_->SetContacts(db_contacts, db_metadata);
db_->set_init_success(false);
// Create a second contact and tell the GData service to return it.
scoped_ptr<Contact> contact2(new Contact);
InitContact("provider2", "2", false, contact2.get());
ContactPointers gdata_contacts;
gdata_contacts.push_back(contact2.get());
gdata_service_->SetContacts(gdata_contacts, base::Time());
// Initialize the store. We shouldn't get the first contact (since DB
// initialization failed) but we should still fetch the second one from GData
// and schedule an update.
store_->Init();
ContactPointers loaded_contacts;
store_->AppendContacts(&loaded_contacts);
EXPECT_EQ(ContactsToString(gdata_contacts),
ContactsToString(loaded_contacts));
EXPECT_TRUE(test_api_->update_scheduled());
}
} // namespace test
} // namespace contacts
...@@ -461,6 +461,10 @@ ...@@ -461,6 +461,10 @@
'browser/chromeos/chrome_browser_main_chromeos.h', 'browser/chromeos/chrome_browser_main_chromeos.h',
'browser/chromeos/contacts/contact_database.cc', 'browser/chromeos/contacts/contact_database.cc',
'browser/chromeos/contacts/contact_database.h', 'browser/chromeos/contacts/contact_database.h',
'browser/chromeos/contacts/contact_store.h',
'browser/chromeos/contacts/contact_store_observer.h',
'browser/chromeos/contacts/google_contact_store.cc',
'browser/chromeos/contacts/google_contact_store.h',
'browser/chromeos/cros/burn_library.cc', 'browser/chromeos/cros/burn_library.cc',
'browser/chromeos/cros/burn_library.h', 'browser/chromeos/cros/burn_library.h',
'browser/chromeos/cros/cellular_data_plan.cc', 'browser/chromeos/cros/cellular_data_plan.cc',
......
...@@ -1086,6 +1086,7 @@ ...@@ -1086,6 +1086,7 @@
'browser/chromeos/contacts/contact_test_util.h', 'browser/chromeos/contacts/contact_test_util.h',
'browser/chromeos/contacts/fake_contact_database.cc', 'browser/chromeos/contacts/fake_contact_database.cc',
'browser/chromeos/contacts/fake_contact_database.h', 'browser/chromeos/contacts/fake_contact_database.h',
'browser/chromeos/contacts/google_contact_store_unittest.cc',
'browser/chromeos/cros/cros_network_functions_unittest.cc', 'browser/chromeos/cros/cros_network_functions_unittest.cc',
'browser/chromeos/cros/network_constants.h', 'browser/chromeos/cros/network_constants.h',
'browser/chromeos/cros/network_library.cc', 'browser/chromeos/cros/network_library.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment