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/google_contact_store.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/files/file_path.h"
     11 #include "base/logging.h"
     12 #include "chrome/browser/browser_process.h"
     13 #include "chrome/browser/chromeos/contacts/contact.pb.h"
     14 #include "chrome/browser/chromeos/contacts/contact_database.h"
     15 #include "chrome/browser/chromeos/contacts/contact_store_observer.h"
     16 #include "chrome/browser/chromeos/contacts/gdata_contacts_service.h"
     17 #include "chrome/browser/chromeos/profiles/profile_util.h"
     18 #include "chrome/browser/google_apis/auth_service.h"
     19 #include "chrome/browser/google_apis/time_util.h"
     20 #include "chrome/browser/profiles/profile.h"
     21 #include "chrome/browser/signin/profile_oauth2_token_service.h"
     22 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     23 #include "content/public/browser/browser_thread.h"
     24 
     25 using content::BrowserThread;
     26 
     27 namespace contacts {
     28 
     29 namespace {
     30 
     31 // Name of the directory within the profile directory where the contact database
     32 // is stored.
     33 const base::FilePath::CharType kDatabaseDirectoryName[] =
     34     FILE_PATH_LITERAL("Google Contacts");
     35 
     36 // We wait this long after the last update has completed successfully before
     37 // updating again.
     38 // TODO(derat): Decide what this should be.
     39 const int kUpdateIntervalSec = 600;
     40 
     41 // https://developers.google.com/google-apps/contacts/v3/index says that deleted
     42 // contact (groups?) will only be returned for 30 days after deletion when the
     43 // "showdeleted" parameter is set. If it's been longer than that since the last
     44 // successful update, we do a full refresh to make sure that we haven't missed
     45 // any deletions. Use 29 instead to make sure that we don't run afoul of
     46 // daylight saving time shenanigans or minor skew in the system clock.
     47 const int kForceFullUpdateDays = 29;
     48 
     49 // When an update fails, we initially wait this many seconds before retrying.
     50 // The delay increases exponentially in response to repeated failures.
     51 const int kUpdateFailureInitialRetrySec = 5;
     52 
     53 // Amount by which |update_delay_on_next_failure_| is multiplied on failure.
     54 const int kUpdateFailureBackoffFactor = 2;
     55 
     56 // OAuth2 scope for the Contacts API.
     57 const char kContactsScope[] = "https://www.google.com/m8/feeds/";
     58 
     59 }  // namespace
     60 
     61 GoogleContactStore::TestAPI::TestAPI(GoogleContactStore* store)
     62     : store_(store) {
     63   DCHECK(store);
     64 }
     65 
     66 GoogleContactStore::TestAPI::~TestAPI() {
     67   store_ = NULL;
     68 }
     69 
     70 void GoogleContactStore::TestAPI::SetDatabase(ContactDatabaseInterface* db) {
     71   store_->DestroyDatabase();
     72   store_->db_ = db;
     73 }
     74 
     75 void GoogleContactStore::TestAPI::SetGDataService(
     76     GDataContactsServiceInterface* service) {
     77   store_->gdata_service_.reset(service);
     78 }
     79 
     80 void GoogleContactStore::TestAPI::DoUpdate() {
     81   store_->UpdateContacts();
     82 }
     83 
     84 void GoogleContactStore::TestAPI::NotifyAboutNetworkStateChange(bool online) {
     85   net::NetworkChangeNotifier::ConnectionType type =
     86       online ?
     87       net::NetworkChangeNotifier::CONNECTION_UNKNOWN :
     88       net::NetworkChangeNotifier::CONNECTION_NONE;
     89   store_->OnConnectionTypeChanged(type);
     90 }
     91 
     92 scoped_ptr<ContactPointers> GoogleContactStore::TestAPI::GetLoadedContacts() {
     93   scoped_ptr<ContactPointers> contacts(new ContactPointers);
     94   for (ContactMap::const_iterator it = store_->contacts_.begin();
     95        it != store_->contacts_.end(); ++it) {
     96     contacts->push_back(it->second);
     97   }
     98   return contacts.Pass();
     99 }
    100 
    101 GoogleContactStore::GoogleContactStore(
    102     net::URLRequestContextGetter* url_request_context_getter,
    103     Profile* profile)
    104     : url_request_context_getter_(url_request_context_getter),
    105       profile_(profile),
    106       db_(new ContactDatabase),
    107       update_delay_on_next_failure_(
    108           base::TimeDelta::FromSeconds(kUpdateFailureInitialRetrySec)),
    109       is_online_(true),
    110       should_update_when_online_(false),
    111       weak_ptr_factory_(this) {
    112   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    113   net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
    114   is_online_ = !net::NetworkChangeNotifier::IsOffline();
    115 }
    116 
    117 GoogleContactStore::~GoogleContactStore() {
    118   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    119   weak_ptr_factory_.InvalidateWeakPtrs();
    120   net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
    121   DestroyDatabase();
    122 }
    123 
    124 void GoogleContactStore::Init() {
    125   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    126 
    127   // Create a GData service if one hasn't already been assigned for testing.
    128   if (!gdata_service_.get()) {
    129     std::vector<std::string> scopes;
    130     scopes.push_back(kContactsScope);
    131 
    132     gdata_service_.reset(new GDataContactsService(
    133         url_request_context_getter_,
    134         new google_apis::AuthService(
    135             ProfileOAuth2TokenServiceFactory::GetForProfile(profile_),
    136             url_request_context_getter_, scopes)));
    137   }
    138 
    139   base::FilePath db_path = profile_->GetPath().Append(kDatabaseDirectoryName);
    140   VLOG(1) << "Initializing contact database \"" << db_path.value() << "\" for "
    141           << profile_->GetProfileName();
    142   db_->Init(db_path,
    143             base::Bind(&GoogleContactStore::OnDatabaseInitialized,
    144                        weak_ptr_factory_.GetWeakPtr()));
    145 }
    146 
    147 void GoogleContactStore::AppendContacts(ContactPointers* contacts_out) {
    148   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    149   DCHECK(contacts_out);
    150   for (ContactMap::const_iterator it = contacts_.begin();
    151        it != contacts_.end(); ++it) {
    152     if (!it->second->deleted())
    153       contacts_out->push_back(it->second);
    154   }
    155 }
    156 
    157 const Contact* GoogleContactStore::GetContactById(
    158     const std::string& contact_id) {
    159   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    160   return contacts_.Find(contact_id);
    161 }
    162 
    163 void GoogleContactStore::AddObserver(ContactStoreObserver* observer) {
    164   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    165   DCHECK(observer);
    166   observers_.AddObserver(observer);
    167 }
    168 
    169 void GoogleContactStore::RemoveObserver(ContactStoreObserver* observer) {
    170   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    171   DCHECK(observer);
    172   observers_.RemoveObserver(observer);
    173 }
    174 
    175 void GoogleContactStore::OnConnectionTypeChanged(
    176     net::NetworkChangeNotifier::ConnectionType type) {
    177   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    178   bool was_online = is_online_;
    179   is_online_ = (type != net::NetworkChangeNotifier::CONNECTION_NONE);
    180   if (!was_online && is_online_ && should_update_when_online_) {
    181     should_update_when_online_ = false;
    182     UpdateContacts();
    183   }
    184 }
    185 
    186 base::Time GoogleContactStore::GetCurrentTime() const {
    187   return !current_time_for_testing_.is_null() ?
    188          current_time_for_testing_ :
    189          base::Time::Now();
    190 }
    191 
    192 void GoogleContactStore::DestroyDatabase() {
    193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    194   if (db_) {
    195     db_->DestroyOnUIThread();
    196     db_ = NULL;
    197   }
    198 }
    199 
    200 void GoogleContactStore::UpdateContacts() {
    201   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    202 
    203   // If we're offline, defer the update.
    204   if (!is_online_) {
    205     VLOG(1) << "Deferring contact update due to offline state";
    206     should_update_when_online_ = true;
    207     return;
    208   }
    209 
    210   base::Time min_update_time;
    211   base::TimeDelta time_since_last_update =
    212       last_successful_update_start_time_.is_null() ?
    213       base::TimeDelta() :
    214       GetCurrentTime() - last_successful_update_start_time_;
    215 
    216   if (!last_contact_update_time_.is_null() &&
    217       time_since_last_update <
    218       base::TimeDelta::FromDays(kForceFullUpdateDays)) {
    219     // TODO(derat): I'm adding one millisecond to the last update time here as I
    220     // don't want to re-download the same most-recently-updated contact each
    221     // time, but what happens if within the same millisecond, contact A is
    222     // updated, we do a sync, and then contact B is updated? I'm probably being
    223     // overly paranoid about this.
    224     min_update_time =
    225         last_contact_update_time_ + base::TimeDelta::FromMilliseconds(1);
    226   }
    227   if (min_update_time.is_null()) {
    228     VLOG(1) << "Downloading all contacts for " << profile_->GetProfileName();
    229   } else {
    230     VLOG(1) << "Downloading contacts updated since "
    231             << google_apis::util::FormatTimeAsString(min_update_time) << " for "
    232             << profile_->GetProfileName();
    233   }
    234 
    235   gdata_service_->DownloadContacts(
    236       base::Bind(&GoogleContactStore::OnDownloadSuccess,
    237                  weak_ptr_factory_.GetWeakPtr(),
    238                  min_update_time.is_null(),
    239                  GetCurrentTime()),
    240       base::Bind(&GoogleContactStore::OnDownloadFailure,
    241                  weak_ptr_factory_.GetWeakPtr()),
    242       min_update_time);
    243 }
    244 
    245 void GoogleContactStore::ScheduleUpdate(bool last_update_was_successful) {
    246   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    247   base::TimeDelta delay;
    248   if (last_update_was_successful) {
    249     delay = base::TimeDelta::FromSeconds(kUpdateIntervalSec);
    250     update_delay_on_next_failure_ =
    251         base::TimeDelta::FromSeconds(kUpdateFailureInitialRetrySec);
    252   } else {
    253     delay = update_delay_on_next_failure_;
    254     update_delay_on_next_failure_ = std::min(
    255         update_delay_on_next_failure_ * kUpdateFailureBackoffFactor,
    256         base::TimeDelta::FromSeconds(kUpdateIntervalSec));
    257   }
    258   VLOG(1) << "Scheduling update of " << profile_->GetProfileName()
    259           << " in " << delay.InSeconds() << " second(s)";
    260   update_timer_.Start(
    261       FROM_HERE, delay, this, &GoogleContactStore::UpdateContacts);
    262 }
    263 
    264 void GoogleContactStore::MergeContacts(
    265     bool is_full_update,
    266     scoped_ptr<ScopedVector<Contact> > updated_contacts) {
    267   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    268 
    269   if (is_full_update) {
    270     contacts_.Clear();
    271     last_contact_update_time_ = base::Time();
    272   }
    273 
    274   // Find the maximum update time from |updated_contacts| since contacts whose
    275   // |deleted| flags are set won't be saved to |contacts_|.
    276   for (ScopedVector<Contact>::iterator it = updated_contacts->begin();
    277        it != updated_contacts->end(); ++it) {
    278     last_contact_update_time_ =
    279         std::max(last_contact_update_time_,
    280                  base::Time::FromInternalValue((*it)->update_time()));
    281   }
    282   VLOG(1) << "Last contact update time is "
    283           << google_apis::util::FormatTimeAsString(last_contact_update_time_);
    284 
    285   contacts_.Merge(updated_contacts.Pass(), ContactMap::DROP_DELETED_CONTACTS);
    286 }
    287 
    288 void GoogleContactStore::OnDownloadSuccess(
    289     bool is_full_update,
    290     const base::Time& update_start_time,
    291     scoped_ptr<ScopedVector<Contact> > updated_contacts) {
    292   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    293   VLOG(1) << "Got " << updated_contacts->size() << " contact(s) for "
    294           << profile_->GetProfileName();
    295 
    296   // Copy the pointers so we can update just these contacts in the database.
    297   scoped_ptr<ContactPointers> contacts_to_save_to_db(new ContactPointers);
    298   scoped_ptr<ContactDatabaseInterface::ContactIds>
    299       contact_ids_to_delete_from_db(new ContactDatabaseInterface::ContactIds);
    300   if (db_) {
    301     for (size_t i = 0; i < updated_contacts->size(); ++i) {
    302       Contact* contact = (*updated_contacts)[i];
    303       if (contact->deleted())
    304         contact_ids_to_delete_from_db->push_back(contact->contact_id());
    305       else
    306         contacts_to_save_to_db->push_back(contact);
    307     }
    308   }
    309   bool got_updates = !updated_contacts->empty();
    310 
    311   MergeContacts(is_full_update, updated_contacts.Pass());
    312   last_successful_update_start_time_ = update_start_time;
    313 
    314   if (is_full_update || got_updates) {
    315     FOR_EACH_OBSERVER(ContactStoreObserver,
    316                       observers_,
    317                       OnContactsUpdated(this));
    318   }
    319 
    320   if (db_) {
    321     // Even if this was an incremental update and we didn't get any updated
    322     // contacts, we still want to write updated metadata containing
    323     // |update_start_time|.
    324     VLOG(1) << "Saving " << contacts_to_save_to_db->size() << " contact(s) to "
    325             << "database and deleting " << contact_ids_to_delete_from_db->size()
    326             << " as " << (is_full_update ? "full" : "incremental") << " update";
    327 
    328     scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata);
    329     metadata->set_last_update_start_time(update_start_time.ToInternalValue());
    330     metadata->set_last_contact_update_time(
    331         last_contact_update_time_.ToInternalValue());
    332 
    333     db_->SaveContacts(
    334         contacts_to_save_to_db.Pass(),
    335         contact_ids_to_delete_from_db.Pass(),
    336         metadata.Pass(),
    337         is_full_update,
    338         base::Bind(&GoogleContactStore::OnDatabaseContactsSaved,
    339                    weak_ptr_factory_.GetWeakPtr()));
    340 
    341     // We'll schedule an update from OnDatabaseContactsSaved() after we're done
    342     // writing to the database -- we don't want to modify the contacts while
    343     // they're being used by the database.
    344   } else {
    345     ScheduleUpdate(true);
    346   }
    347 }
    348 
    349 void GoogleContactStore::OnDownloadFailure() {
    350   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    351   LOG(WARNING) << "Contacts download failed for " << profile_->GetProfileName();
    352   ScheduleUpdate(false);
    353 }
    354 
    355 void GoogleContactStore::OnDatabaseInitialized(bool success) {
    356   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    357   if (success) {
    358     VLOG(1) << "Contact database initialized for "
    359             << profile_->GetProfileName();
    360     db_->LoadContacts(base::Bind(&GoogleContactStore::OnDatabaseContactsLoaded,
    361                                  weak_ptr_factory_.GetWeakPtr()));
    362   } else {
    363     LOG(WARNING) << "Failed to initialize contact database for "
    364                  << profile_->GetProfileName();
    365     // Limp along as best as we can: throw away the database and do an update,
    366     // which will schedule further updates.
    367     DestroyDatabase();
    368     UpdateContacts();
    369   }
    370 }
    371 
    372 void GoogleContactStore::OnDatabaseContactsLoaded(
    373     bool success,
    374     scoped_ptr<ScopedVector<Contact> > contacts,
    375     scoped_ptr<UpdateMetadata> metadata) {
    376   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    377   if (success) {
    378     VLOG(1) << "Loaded " << contacts->size() << " contact(s) from database";
    379     MergeContacts(true, contacts.Pass());
    380     last_successful_update_start_time_ =
    381         base::Time::FromInternalValue(metadata->last_update_start_time());
    382     last_contact_update_time_ = std::max(
    383         last_contact_update_time_,
    384         base::Time::FromInternalValue(metadata->last_contact_update_time()));
    385 
    386     if (!contacts_.empty()) {
    387       FOR_EACH_OBSERVER(ContactStoreObserver,
    388                         observers_,
    389                         OnContactsUpdated(this));
    390     }
    391   } else {
    392     LOG(WARNING) << "Failed to load contacts from database";
    393   }
    394   UpdateContacts();
    395 }
    396 
    397 void GoogleContactStore::OnDatabaseContactsSaved(bool success) {
    398   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    399   if (!success)
    400     LOG(WARNING) << "Failed to save contacts to database";
    401 
    402   // We only update the database when we've successfully downloaded contacts, so
    403   // report success to ScheduleUpdate() even if the database update failed.
    404   ScheduleUpdate(true);
    405 }
    406 
    407 GoogleContactStoreFactory::GoogleContactStoreFactory() {
    408 }
    409 
    410 GoogleContactStoreFactory::~GoogleContactStoreFactory() {
    411 }
    412 
    413 bool GoogleContactStoreFactory::CanCreateContactStoreForProfile(
    414     Profile* profile) {
    415   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    416   DCHECK(profile);
    417   return chromeos::IsProfileAssociatedWithGaiaAccount(profile);
    418 }
    419 
    420 ContactStore* GoogleContactStoreFactory::CreateContactStore(Profile* profile) {
    421   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    422   DCHECK(CanCreateContactStoreForProfile(profile));
    423   return new GoogleContactStore(
    424       g_browser_process->system_request_context(), profile);
    425 }
    426 
    427 }  // namespace contacts
    428