OLD | NEW |
(Empty) | |
| 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_source.h" |
| 6 |
| 7 #include <string> |
| 8 |
| 9 #include "base/bind.h" |
| 10 #include "base/file_path.h" |
| 11 #include "base/logging.h" |
| 12 #include "base/stringprintf.h" |
| 13 #include "base/tracked_objects.h" |
| 14 #include "chrome/browser/chromeos/contacts/contact.h" |
| 15 #include "chrome/browser/chromeos/contacts/contact_database.h" |
| 16 #include "chrome/browser/chromeos/contacts/contact_source_observer.h" |
| 17 #include "chrome/browser/chromeos/gdata/gdata_contacts_service.h" |
| 18 #include "chrome/browser/chromeos/gdata/gdata_system_service.h" |
| 19 #include "chrome/browser/chromeos/gdata/gdata_util.h" |
| 20 #include "chrome/browser/profiles/profile.h" |
| 21 #include "content/public/browser/browser_thread.h" |
| 22 |
| 23 using content::BrowserThread; |
| 24 |
| 25 namespace contacts { |
| 26 |
| 27 namespace { |
| 28 |
| 29 // Name of the file within the profile directory where contacts are written. |
| 30 const FilePath::CharType kDatabaseFileName[] = |
| 31 FILE_PATH_LITERAL("Google Contacts"); |
| 32 |
| 33 // We wait this long after the last update has completed before updating again. |
| 34 // FIXME: set this to a non-insane value |
| 35 const int kUpdateIntervalSec = 60; |
| 36 |
| 37 } // namespace |
| 38 |
| 39 GoogleContactSource::TestAPI::TestAPI(GoogleContactSource* source) |
| 40 : source_(source) { |
| 41 DCHECK(source); |
| 42 } |
| 43 |
| 44 GoogleContactSource::TestAPI::~TestAPI() { |
| 45 source_ = NULL; |
| 46 } |
| 47 |
| 48 void GoogleContactSource::TestAPI::SetDatabase(ContactDatabaseInterface* db) { |
| 49 source_->db_.reset(db); |
| 50 } |
| 51 |
| 52 void GoogleContactSource::TestAPI::SetGDataService( |
| 53 gdata::GDataContactsServiceInterface* service) { |
| 54 source_->gdata_service_for_testing_.reset(service); |
| 55 } |
| 56 |
| 57 void GoogleContactSource::TestAPI::DoUpdate() { |
| 58 source_->UpdateContacts(); |
| 59 } |
| 60 |
| 61 GoogleContactSource::GoogleContactSource(Profile* profile) |
| 62 : profile_(profile), |
| 63 contacts_deleter_(&contacts_), |
| 64 db_(new ContactDatabase) { |
| 65 } |
| 66 |
| 67 GoogleContactSource::~GoogleContactSource() { |
| 68 } |
| 69 |
| 70 void GoogleContactSource::Init() { |
| 71 VLOG(1) << "Initializing contact database for " << profile_->GetProfileName(); |
| 72 db_->Init(profile_->GetPath().Append(kDatabaseFileName), |
| 73 base::Bind(&GoogleContactSource::OnDatabaseInitialized, |
| 74 base::Unretained(this))); |
| 75 } |
| 76 |
| 77 void GoogleContactSource::AppendContacts(ContactPointers* contacts_out) { |
| 78 DCHECK(contacts_out); |
| 79 for (ContactMap::const_iterator it = contacts_.begin(); |
| 80 it != contacts_.end(); ++it) { |
| 81 if (!it->second->deleted) |
| 82 contacts_out->push_back(it->second); |
| 83 } |
| 84 } |
| 85 |
| 86 const Contact* GoogleContactSource::GetContactByProviderId( |
| 87 const std::string& provider_id) { |
| 88 ContactMap::const_iterator it = contacts_.find(provider_id); |
| 89 return it != contacts_.end() ? it->second : NULL; |
| 90 } |
| 91 |
| 92 void GoogleContactSource::AddObserver(ContactSourceObserver* observer) { |
| 93 DCHECK(observer); |
| 94 observers_.AddObserver(observer); |
| 95 } |
| 96 |
| 97 void GoogleContactSource::RemoveObserver(ContactSourceObserver* observer) { |
| 98 DCHECK(observer); |
| 99 observers_.RemoveObserver(observer); |
| 100 } |
| 101 |
| 102 void GoogleContactSource::UpdateContacts() { |
| 103 // TODO(derat): Need to do a full sync when it's been more than 30 days |
| 104 // since the last update? |
| 105 // https://developers.google.com/google-apps/contacts/v3/index says that |
| 106 // deleted contact (groups?) will only be returned for 30 days after |
| 107 // deletion when the "showdeleted" parameter is set. |
| 108 base::Time min_update_time; |
| 109 if (!last_update_time_.is_null()) { |
| 110 // TODO(derat): Adding one millisecond to the last update time here as I |
| 111 // don't want to re-download the same most-recently-updated contact each |
| 112 // time, but what happens if within the same millisecond, contact A is |
| 113 // updated, we do a sync, and then contact B is updated? I'm probably |
| 114 // being overly paranoid about this. |
| 115 min_update_time = |
| 116 last_update_time_ + base::TimeDelta::FromMilliseconds(1); |
| 117 } |
| 118 if (min_update_time.is_null()) { |
| 119 VLOG(1) << "Downloading all contacts for " << profile_->GetProfileName(); |
| 120 } else { |
| 121 VLOG(1) << "Downloading contacts for " << profile_->GetProfileName() |
| 122 << " modified since " |
| 123 << gdata::util::FormatTimeAsString(min_update_time); |
| 124 } |
| 125 |
| 126 gdata::GDataContactsServiceInterface* service = |
| 127 gdata_service_for_testing_.get() ? |
| 128 gdata_service_for_testing_.get() : |
| 129 gdata::GDataSystemServiceFactory::GetForProfile(profile_)-> |
| 130 contacts_service(); |
| 131 DCHECK(service); |
| 132 service->DownloadContacts( |
| 133 base::Bind(&GoogleContactSource::OnDownloadSuccess, |
| 134 base::Unretained(this), |
| 135 min_update_time.is_null()), |
| 136 base::Bind(&GoogleContactSource::OnDownloadFailure, |
| 137 base::Unretained(this)), |
| 138 min_update_time); |
| 139 } |
| 140 |
| 141 void GoogleContactSource::ScheduleUpdate() { |
| 142 update_timer_.Start( |
| 143 FROM_HERE, base::TimeDelta::FromSeconds(kUpdateIntervalSec), |
| 144 this, &GoogleContactSource::UpdateContacts); |
| 145 } |
| 146 |
| 147 void GoogleContactSource::MergeContacts( |
| 148 bool is_full_update, |
| 149 ScopedVector<Contact>* updated_contacts) { |
| 150 if (is_full_update) |
| 151 contacts_.clear(); |
| 152 |
| 153 size_t num_updated_contacts = updated_contacts->size(); |
| 154 for (ScopedVector<Contact>::iterator it = updated_contacts->begin(); |
| 155 it != updated_contacts->end(); ++it) { |
| 156 Contact* contact = *it; |
| 157 VLOG(1) << "Updating " << contact->provider_id; |
| 158 ContactMap::iterator map_it = contacts_.find(contact->provider_id); |
| 159 if (map_it == contacts_.end()) { |
| 160 contacts_[contact->provider_id] = contact; |
| 161 } else { |
| 162 delete map_it->second; |
| 163 map_it->second = contact; |
| 164 } |
| 165 } |
| 166 updated_contacts->weak_clear(); |
| 167 |
| 168 if (is_full_update || num_updated_contacts > 0) { |
| 169 // Find the latest update time. |
| 170 last_update_time_ = base::Time(); |
| 171 for (ContactMap::const_iterator it = contacts_.begin(); |
| 172 it != contacts_.end(); ++it) { |
| 173 const Contact* contact = it->second; |
| 174 if (!contact->update_time.is_null() && |
| 175 (last_update_time_.is_null() || |
| 176 last_update_time_ < contact->update_time)) { |
| 177 last_update_time_ = contact->update_time; |
| 178 } |
| 179 } |
| 180 } |
| 181 VLOG(1) << "Last update time is " |
| 182 << (last_update_time_.is_null() ? |
| 183 std::string("null") : |
| 184 gdata::util::FormatTimeAsString(last_update_time_)); |
| 185 } |
| 186 |
| 187 void GoogleContactSource::OnDownloadSuccess( |
| 188 bool is_full_update, |
| 189 scoped_ptr<ScopedVector<Contact> > updated_contacts) { |
| 190 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 191 VLOG(1) << "Got " << updated_contacts->size() << " contact(s) for " |
| 192 << profile_->GetProfileName(); |
| 193 size_t num_updated_contacts = updated_contacts->size(); |
| 194 MergeContacts(is_full_update, updated_contacts.get()); |
| 195 |
| 196 bool changed = is_full_update || num_updated_contacts > 0; |
| 197 if (changed) { |
| 198 FOR_EACH_OBSERVER(ContactSourceObserver, |
| 199 observers_, |
| 200 OnContactsUpdated(this)); |
| 201 } |
| 202 |
| 203 if (changed && db_.get()) { |
| 204 scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers); |
| 205 for (ContactMap::const_iterator it = contacts_.begin(); |
| 206 it != contacts_.end(); ++it) { |
| 207 contacts_to_save->push_back(it->second); |
| 208 } |
| 209 db_->SaveContacts( |
| 210 contacts_to_save.Pass(), |
| 211 is_full_update, |
| 212 base::Bind(&GoogleContactSource::OnDatabaseContactsSaved, |
| 213 base::Unretained(this))); |
| 214 // We'll schedule an update from OnDatabaseContactsSaved() after we're doing |
| 215 // writing to the database -- we don't want to modify the contacts while |
| 216 // they're being used by the database. |
| 217 } else { |
| 218 ScheduleUpdate(); |
| 219 } |
| 220 } |
| 221 |
| 222 void GoogleContactSource::OnDownloadFailure() { |
| 223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 224 LOG(WARNING) << "Contacts download failed for " << profile_->GetProfileName(); |
| 225 ScheduleUpdate(); |
| 226 } |
| 227 |
| 228 void GoogleContactSource::OnDatabaseInitialized(bool success) { |
| 229 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 230 if (success) { |
| 231 VLOG(1) << "Contact database initialized for " |
| 232 << profile_->GetProfileName(); |
| 233 db_->LoadContacts( |
| 234 base::Bind(&GoogleContactSource::OnDatabaseContactsLoaded, |
| 235 base::Unretained(this))); |
| 236 } else { |
| 237 LOG(WARNING) << "Failed to initialize contact database for " |
| 238 << profile_->GetProfileName(); |
| 239 // Limp along as best as we can: throw away the database and do an update, |
| 240 // which will schedule further updates. |
| 241 db_.reset(); |
| 242 UpdateContacts(); |
| 243 } |
| 244 } |
| 245 |
| 246 void GoogleContactSource::OnDatabaseContactsLoaded( |
| 247 bool success, scoped_ptr<ScopedVector<Contact> > contacts) { |
| 248 if (success) { |
| 249 VLOG(1) << "Loaded " << contacts->size() << " contact(s) from database"; |
| 250 MergeContacts(true, contacts.get()); |
| 251 if (!contacts_.empty()) { |
| 252 FOR_EACH_OBSERVER(ContactSourceObserver, |
| 253 observers_, |
| 254 OnContactsUpdated(this)); |
| 255 } |
| 256 } else { |
| 257 LOG(WARNING) << "Failed to load contacts from database"; |
| 258 } |
| 259 UpdateContacts(); |
| 260 } |
| 261 |
| 262 void GoogleContactSource::OnDatabaseContactsSaved(bool success) { |
| 263 if (!success) |
| 264 LOG(WARNING) << "Failed to save contacts to database"; |
| 265 ScheduleUpdate(); |
| 266 } |
| 267 |
| 268 } // namespace contacts |
OLD | NEW |