Index: chrome/browser/chromeos/contacts/google_contact_source.cc |
diff --git a/chrome/browser/chromeos/contacts/google_contact_source.cc b/chrome/browser/chromeos/contacts/google_contact_source.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f86c1fc7d3c6088bdb0bb2ea0b923762e3034b36 |
--- /dev/null |
+++ b/chrome/browser/chromeos/contacts/google_contact_source.cc |
@@ -0,0 +1,268 @@ |
+// 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_source.h" |
+ |
+#include <string> |
+ |
+#include "base/bind.h" |
+#include "base/file_path.h" |
+#include "base/logging.h" |
+#include "base/stringprintf.h" |
+#include "base/tracked_objects.h" |
+#include "chrome/browser/chromeos/contacts/contact.h" |
+#include "chrome/browser/chromeos/contacts/contact_database.h" |
+#include "chrome/browser/chromeos/contacts/contact_source_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 file within the profile directory where contacts are written. |
+const FilePath::CharType kDatabaseFileName[] = |
+ FILE_PATH_LITERAL("Google Contacts"); |
+ |
+// We wait this long after the last update has completed before updating again. |
+// FIXME: set this to a non-insane value |
+const int kUpdateIntervalSec = 60; |
+ |
+} // namespace |
+ |
+GoogleContactSource::TestAPI::TestAPI(GoogleContactSource* source) |
+ : source_(source) { |
+ DCHECK(source); |
+} |
+ |
+GoogleContactSource::TestAPI::~TestAPI() { |
+ source_ = NULL; |
+} |
+ |
+void GoogleContactSource::TestAPI::SetDatabase(ContactDatabaseInterface* db) { |
+ source_->db_.reset(db); |
+} |
+ |
+void GoogleContactSource::TestAPI::SetGDataService( |
+ gdata::GDataContactsServiceInterface* service) { |
+ source_->gdata_service_for_testing_.reset(service); |
+} |
+ |
+void GoogleContactSource::TestAPI::DoUpdate() { |
+ source_->UpdateContacts(); |
+} |
+ |
+GoogleContactSource::GoogleContactSource(Profile* profile) |
+ : profile_(profile), |
+ contacts_deleter_(&contacts_), |
+ db_(new ContactDatabase) { |
+} |
+ |
+GoogleContactSource::~GoogleContactSource() { |
+} |
+ |
+void GoogleContactSource::Init() { |
+ VLOG(1) << "Initializing contact database for " << profile_->GetProfileName(); |
+ db_->Init(profile_->GetPath().Append(kDatabaseFileName), |
+ base::Bind(&GoogleContactSource::OnDatabaseInitialized, |
+ base::Unretained(this))); |
+} |
+ |
+void GoogleContactSource::AppendContacts(ContactPointers* contacts_out) { |
+ 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* GoogleContactSource::GetContactByProviderId( |
+ const std::string& provider_id) { |
+ ContactMap::const_iterator it = contacts_.find(provider_id); |
+ return it != contacts_.end() ? it->second : NULL; |
+} |
+ |
+void GoogleContactSource::AddObserver(ContactSourceObserver* observer) { |
+ DCHECK(observer); |
+ observers_.AddObserver(observer); |
+} |
+ |
+void GoogleContactSource::RemoveObserver(ContactSourceObserver* observer) { |
+ DCHECK(observer); |
+ observers_.RemoveObserver(observer); |
+} |
+ |
+void GoogleContactSource::UpdateContacts() { |
+ // TODO(derat): Need to do a full sync when it's been more than 30 days |
+ // since the last update? |
+ // 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. |
+ base::Time min_update_time; |
+ if (!last_update_time_.is_null()) { |
+ // TODO(derat): 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_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 for " << profile_->GetProfileName() |
+ << " modified since " |
+ << gdata::util::FormatTimeAsString(min_update_time); |
+ } |
+ |
+ 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(&GoogleContactSource::OnDownloadSuccess, |
+ base::Unretained(this), |
+ min_update_time.is_null()), |
+ base::Bind(&GoogleContactSource::OnDownloadFailure, |
+ base::Unretained(this)), |
+ min_update_time); |
+} |
+ |
+void GoogleContactSource::ScheduleUpdate() { |
+ update_timer_.Start( |
+ FROM_HERE, base::TimeDelta::FromSeconds(kUpdateIntervalSec), |
+ this, &GoogleContactSource::UpdateContacts); |
+} |
+ |
+void GoogleContactSource::MergeContacts( |
+ bool is_full_update, |
+ ScopedVector<Contact>* updated_contacts) { |
+ if (is_full_update) |
+ contacts_.clear(); |
+ |
+ size_t num_updated_contacts = updated_contacts->size(); |
+ 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; |
+ } |
+ } |
+ updated_contacts->weak_clear(); |
+ |
+ if (is_full_update || num_updated_contacts > 0) { |
+ // Find the latest update time. |
+ last_update_time_ = base::Time(); |
+ for (ContactMap::const_iterator it = contacts_.begin(); |
+ it != contacts_.end(); ++it) { |
+ const Contact* contact = it->second; |
+ if (!contact->update_time.is_null() && |
+ (last_update_time_.is_null() || |
+ last_update_time_ < contact->update_time)) { |
+ last_update_time_ = contact->update_time; |
+ } |
+ } |
+ } |
+ VLOG(1) << "Last update time is " |
+ << (last_update_time_.is_null() ? |
+ std::string("null") : |
+ gdata::util::FormatTimeAsString(last_update_time_)); |
+} |
+ |
+void GoogleContactSource::OnDownloadSuccess( |
+ bool is_full_update, |
+ 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.get()); |
+ |
+ bool changed = is_full_update || num_updated_contacts > 0; |
+ if (changed) { |
+ FOR_EACH_OBSERVER(ContactSourceObserver, |
+ observers_, |
+ OnContactsUpdated(this)); |
+ } |
+ |
+ if (changed && db_.get()) { |
+ 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); |
+ } |
+ db_->SaveContacts( |
+ contacts_to_save.Pass(), |
+ is_full_update, |
+ base::Bind(&GoogleContactSource::OnDatabaseContactsSaved, |
+ base::Unretained(this))); |
+ // We'll schedule an update from OnDatabaseContactsSaved() after we're doing |
+ // writing to the database -- we don't want to modify the contacts while |
+ // they're being used by the database. |
+ } else { |
+ ScheduleUpdate(); |
+ } |
+} |
+ |
+void GoogleContactSource::OnDownloadFailure() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ LOG(WARNING) << "Contacts download failed for " << profile_->GetProfileName(); |
+ ScheduleUpdate(); |
+} |
+ |
+void GoogleContactSource::OnDatabaseInitialized(bool success) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ if (success) { |
+ VLOG(1) << "Contact database initialized for " |
+ << profile_->GetProfileName(); |
+ db_->LoadContacts( |
+ base::Bind(&GoogleContactSource::OnDatabaseContactsLoaded, |
+ base::Unretained(this))); |
+ } 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. |
+ db_.reset(); |
+ UpdateContacts(); |
+ } |
+} |
+ |
+void GoogleContactSource::OnDatabaseContactsLoaded( |
+ bool success, scoped_ptr<ScopedVector<Contact> > contacts) { |
+ if (success) { |
+ VLOG(1) << "Loaded " << contacts->size() << " contact(s) from database"; |
+ MergeContacts(true, contacts.get()); |
+ if (!contacts_.empty()) { |
+ FOR_EACH_OBSERVER(ContactSourceObserver, |
+ observers_, |
+ OnContactsUpdated(this)); |
+ } |
+ } else { |
+ LOG(WARNING) << "Failed to load contacts from database"; |
+ } |
+ UpdateContacts(); |
+} |
+ |
+void GoogleContactSource::OnDatabaseContactsSaved(bool success) { |
+ if (!success) |
+ LOG(WARNING) << "Failed to save contacts to database"; |
+ ScheduleUpdate(); |
+} |
+ |
+} // namespace contacts |