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