Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(299)

Side by Side Diff: chrome/browser/chromeos/contacts/google_contact_store.cc

Issue 10829144: contacts: Add GoogleContactStore. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fix leaked contacts in GoogleContactStore::MergeContacts() Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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_store.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/file_path.h"
11 #include "base/logging.h"
12 #include "chrome/browser/chromeos/contacts/contact.pb.h"
13 #include "chrome/browser/chromeos/contacts/contact_database.h"
14 #include "chrome/browser/chromeos/contacts/contact_store_observer.h"
15 #include "chrome/browser/chromeos/gdata/gdata_contacts_service.h"
16 #include "chrome/browser/chromeos/gdata/gdata_system_service.h"
17 #include "chrome/browser/chromeos/gdata/gdata_util.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "content/public/browser/browser_thread.h"
20
21 using content::BrowserThread;
22
23 namespace contacts {
24
25 namespace {
26
27 // Name of the directory within the profile directory where the contact database
28 // is stored.
29 const FilePath::CharType kDatabaseDirectoryName[] =
30 FILE_PATH_LITERAL("Google Contacts");
31
32 // We wait this long after the last update has completed successfully before
33 // updating again.
34 // TODO(derat): Decide what this should be.
35 const int kUpdateIntervalSec = 600;
36
37 // https://developers.google.com/google-apps/contacts/v3/index says that deleted
38 // contact (groups?) will only be returned for 30 days after deletion when the
39 // "showdeleted" parameter is set. If it's been longer than that since the last
40 // successful update, we do a full refresh to make sure that we haven't missed
41 // any deletions. Use 29 instead to make sure that we don't run afoul of
42 // daylight saving time shenanigans or minor skew in the system clock.
43 const int kForceFullUpdateDays = 29;
44
45 // When an update fails, we initially wait this many seconds before retrying.
46 // The delay increases exponentially in response to repeated failures.
47 const int kUpdateFailureInitialRetrySec = 5;
48
49 // Amount by which |update_delay_on_next_failure_| is multiplied on failure.
50 const int kUpdateFailureBackoffFactor = 2;
51
52 } // namespace
53
54 GoogleContactStore::TestAPI::TestAPI(GoogleContactStore* store)
55 : store_(store) {
56 DCHECK(store);
57 }
58
59 GoogleContactStore::TestAPI::~TestAPI() {
60 store_ = NULL;
61 }
62
63 void GoogleContactStore::TestAPI::SetDatabase(ContactDatabaseInterface* db) {
64 store_->DestroyDatabase();
65 store_->db_ = db;
66 }
67
68 void GoogleContactStore::TestAPI::SetGDataService(
69 gdata::GDataContactsServiceInterface* service) {
70 store_->gdata_service_for_testing_.reset(service);
71 }
72
73 void GoogleContactStore::TestAPI::DoUpdate() {
74 store_->UpdateContacts();
75 }
76
77 GoogleContactStore::GoogleContactStore(Profile* profile)
78 : profile_(profile),
79 contacts_deleter_(&contacts_),
80 db_(new ContactDatabase),
81 update_delay_on_next_failure_(
82 base::TimeDelta::FromSeconds(kUpdateFailureInitialRetrySec)),
83 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
84 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85 }
86
87 GoogleContactStore::~GoogleContactStore() {
88 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
89 weak_ptr_factory_.InvalidateWeakPtrs();
90 DestroyDatabase();
91 }
92
93 void GoogleContactStore::Init() {
94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
95 FilePath db_path = profile_->GetPath().Append(kDatabaseDirectoryName);
96 VLOG(1) << "Initializing contact database \"" << db_path.value() << "\" for "
97 << profile_->GetProfileName();
98 db_->Init(db_path,
99 base::Bind(&GoogleContactStore::OnDatabaseInitialized,
100 weak_ptr_factory_.GetWeakPtr()));
101 }
102
103 void GoogleContactStore::AppendContacts(ContactPointers* contacts_out) {
104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
105 DCHECK(contacts_out);
106 for (ContactMap::const_iterator it = contacts_.begin();
107 it != contacts_.end(); ++it) {
108 if (!it->second->deleted())
109 contacts_out->push_back(it->second);
110 }
111 }
112
113 const Contact* GoogleContactStore::GetContactByProviderId(
114 const std::string& provider_id) {
115 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
116 ContactMap::const_iterator it = contacts_.find(provider_id);
117 return (it != contacts_.end() && !it->second->deleted()) ? it->second : NULL;
118 }
119
120 void GoogleContactStore::AddObserver(ContactStoreObserver* observer) {
121 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
122 DCHECK(observer);
123 observers_.AddObserver(observer);
124 }
125
126 void GoogleContactStore::RemoveObserver(ContactStoreObserver* observer) {
127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
128 DCHECK(observer);
129 observers_.RemoveObserver(observer);
130 }
131
132 base::Time GoogleContactStore::GetCurrentTime() const {
133 return !current_time_for_testing_.is_null() ?
134 current_time_for_testing_ :
135 base::Time::Now();
136 }
137
138 void GoogleContactStore::DestroyDatabase() {
139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
140 if (db_) {
141 db_->DestroyOnUIThread();
142 db_ = NULL;
143 }
144 }
145
146 void GoogleContactStore::UpdateContacts() {
147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
148 base::Time min_update_time;
149 base::TimeDelta time_since_last_update =
150 last_successful_update_start_time_.is_null() ?
151 base::TimeDelta() :
152 GetCurrentTime() - last_successful_update_start_time_;
153
154 if (!last_contact_update_time_.is_null() &&
155 time_since_last_update <
156 base::TimeDelta::FromDays(kForceFullUpdateDays)) {
157 // TODO(derat): I'm adding one millisecond to the last update time here as I
158 // don't want to re-download the same most-recently-updated contact each
159 // time, but what happens if within the same millisecond, contact A is
160 // updated, we do a sync, and then contact B is updated? I'm probably being
161 // overly paranoid about this.
162 min_update_time =
163 last_contact_update_time_ + base::TimeDelta::FromMilliseconds(1);
164 }
165 if (min_update_time.is_null()) {
166 VLOG(1) << "Downloading all contacts for " << profile_->GetProfileName();
167 } else {
168 VLOG(1) << "Downloading contacts updated since "
169 << gdata::util::FormatTimeAsString(min_update_time) << " for "
170 << profile_->GetProfileName();
171 }
172
173 gdata::GDataContactsServiceInterface* service =
174 gdata_service_for_testing_.get() ?
175 gdata_service_for_testing_.get() :
176 gdata::GDataSystemServiceFactory::GetForProfile(profile_)->
177 contacts_service();
178 DCHECK(service);
179 service->DownloadContacts(
180 base::Bind(&GoogleContactStore::OnDownloadSuccess,
181 weak_ptr_factory_.GetWeakPtr(),
182 min_update_time.is_null(),
183 GetCurrentTime()),
184 base::Bind(&GoogleContactStore::OnDownloadFailure,
185 weak_ptr_factory_.GetWeakPtr()),
186 min_update_time);
187 }
188
189 void GoogleContactStore::ScheduleUpdate(bool last_update_was_successful) {
190 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
191 base::TimeDelta delay;
192 if (last_update_was_successful) {
193 delay = base::TimeDelta::FromSeconds(kUpdateIntervalSec);
194 update_delay_on_next_failure_ =
195 base::TimeDelta::FromSeconds(kUpdateFailureInitialRetrySec);
196 } else {
197 delay = update_delay_on_next_failure_;
198 update_delay_on_next_failure_ = std::min(
199 update_delay_on_next_failure_ * kUpdateFailureBackoffFactor,
200 base::TimeDelta::FromSeconds(kUpdateIntervalSec));
201 }
202 VLOG(1) << "Scheduling update of " << profile_->GetProfileName()
203 << " in " << delay.InSeconds() << " second(s)";
204 update_timer_.Start(
205 FROM_HERE, delay, this, &GoogleContactStore::UpdateContacts);
206 }
207
208 void GoogleContactStore::MergeContacts(
209 bool is_full_update,
210 scoped_ptr<ScopedVector<Contact> > updated_contacts) {
211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
212 if (is_full_update) {
213 STLDeleteValues(&contacts_);
214 contacts_.clear();
215 }
216
217 for (ScopedVector<Contact>::iterator it = updated_contacts->begin();
218 it != updated_contacts->end(); ++it) {
219 Contact* contact = *it;
220 VLOG(1) << "Updating " << contact->provider_id();
221 ContactMap::iterator map_it = contacts_.find(contact->provider_id());
222 if (map_it == contacts_.end()) {
223 contacts_[contact->provider_id()] = contact;
224 } else {
225 delete map_it->second;
226 map_it->second = contact;
227 }
228 }
229
230 // Make sure that the Contact objects won't be destroyed when
231 // |updated_contacts| is destroyed.
232 size_t num_updated_contacts = updated_contacts->size();
233 updated_contacts->weak_clear();
234
235 if (is_full_update || num_updated_contacts > 0) {
236 // Find the latest update time.
237 last_contact_update_time_ = base::Time();
238 for (ContactMap::const_iterator it = contacts_.begin();
239 it != contacts_.end(); ++it) {
240 const Contact* contact = it->second;
241 base::Time update_time =
242 base::Time::FromInternalValue(contact->update_time());
243
244 if (!update_time.is_null() &&
245 (last_contact_update_time_.is_null() ||
246 last_contact_update_time_ < update_time)) {
247 last_contact_update_time_ = update_time;
248 }
249 }
250 }
251 VLOG(1) << "Last contact update time is "
252 << (last_contact_update_time_.is_null() ?
253 std::string("null") :
254 gdata::util::FormatTimeAsString(last_contact_update_time_));
255 }
256
257 void GoogleContactStore::OnDownloadSuccess(
258 bool is_full_update,
259 const base::Time& update_start_time,
260 scoped_ptr<ScopedVector<Contact> > updated_contacts) {
261 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
262 VLOG(1) << "Got " << updated_contacts->size() << " contact(s) for "
263 << profile_->GetProfileName();
264 size_t num_updated_contacts = updated_contacts->size();
265 MergeContacts(is_full_update, updated_contacts.Pass());
266 last_successful_update_start_time_ = update_start_time;
267
268 if (is_full_update || num_updated_contacts > 0) {
269 FOR_EACH_OBSERVER(ContactStoreObserver,
270 observers_,
271 OnContactsUpdated(this));
272 }
273
274 if (db_) {
275 scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
276 for (ContactMap::const_iterator it = contacts_.begin();
277 it != contacts_.end(); ++it) {
278 contacts_to_save->push_back(it->second);
279 }
280 scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata);
281 metadata->set_last_update_start_time(update_start_time.ToInternalValue());
282
283 db_->SaveContacts(
284 contacts_to_save.Pass(),
285 metadata.Pass(),
286 is_full_update,
287 base::Bind(&GoogleContactStore::OnDatabaseContactsSaved,
288 weak_ptr_factory_.GetWeakPtr()));
289
290 // We'll schedule an update from OnDatabaseContactsSaved() after we're done
291 // writing to the database -- we don't want to modify the contacts while
292 // they're being used by the database.
293 } else {
294 ScheduleUpdate(true);
295 }
296 }
297
298 void GoogleContactStore::OnDownloadFailure() {
299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300 LOG(WARNING) << "Contacts download failed for " << profile_->GetProfileName();
301 ScheduleUpdate(false);
302 }
303
304 void GoogleContactStore::OnDatabaseInitialized(bool success) {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 if (success) {
307 VLOG(1) << "Contact database initialized for "
308 << profile_->GetProfileName();
309 db_->LoadContacts(base::Bind(&GoogleContactStore::OnDatabaseContactsLoaded,
310 weak_ptr_factory_.GetWeakPtr()));
311 } else {
312 LOG(WARNING) << "Failed to initialize contact database for "
313 << profile_->GetProfileName();
314 // Limp along as best as we can: throw away the database and do an update,
315 // which will schedule further updates.
316 DestroyDatabase();
317 UpdateContacts();
318 }
319 }
320
321 void GoogleContactStore::OnDatabaseContactsLoaded(
322 bool success,
323 scoped_ptr<ScopedVector<Contact> > contacts,
324 scoped_ptr<UpdateMetadata> metadata) {
325 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
326 if (success) {
327 VLOG(1) << "Loaded " << contacts->size() << " contact(s) from database";
328 MergeContacts(true, contacts.Pass());
329 last_successful_update_start_time_ =
330 base::Time::FromInternalValue(metadata->last_update_start_time());
331
332 if (!contacts_.empty()) {
333 FOR_EACH_OBSERVER(ContactStoreObserver,
334 observers_,
335 OnContactsUpdated(this));
336 }
337 } else {
338 LOG(WARNING) << "Failed to load contacts from database";
339 }
340 UpdateContacts();
341 }
342
343 void GoogleContactStore::OnDatabaseContactsSaved(bool success) {
344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
345 if (!success)
346 LOG(WARNING) << "Failed to save contacts to database";
347
348 // We only update the database when we've successfully downloaded contacts, so
349 // report success to ScheduleUpdate() even if the database update failed.
350 ScheduleUpdate(true);
351 }
352
353 } // namespace contacts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698