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

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: condense private and protected blocks' 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 contacts_.clear();
214
215 for (ScopedVector<Contact>::iterator it = updated_contacts->begin();
216 it != updated_contacts->end(); ++it) {
217 Contact* contact = *it;
218 VLOG(1) << "Updating " << contact->provider_id();
219 ContactMap::iterator map_it = contacts_.find(contact->provider_id());
220 if (map_it == contacts_.end()) {
221 contacts_[contact->provider_id()] = contact;
222 } else {
223 delete map_it->second;
224 map_it->second = contact;
225 }
226 }
227
228 // Make sure that the Contact objects won't be destroyed when
229 // |updated_contacts| is destroyed.
230 size_t num_updated_contacts = updated_contacts->size();
231 updated_contacts->weak_clear();
232
233 if (is_full_update || num_updated_contacts > 0) {
234 // Find the latest update time.
235 last_contact_update_time_ = base::Time();
236 for (ContactMap::const_iterator it = contacts_.begin();
237 it != contacts_.end(); ++it) {
238 const Contact* contact = it->second;
239 base::Time update_time =
240 base::Time::FromInternalValue(contact->update_time());
241
242 if (!update_time.is_null() &&
243 (last_contact_update_time_.is_null() ||
244 last_contact_update_time_ < update_time)) {
245 last_contact_update_time_ = update_time;
246 }
247 }
248 }
249 VLOG(1) << "Last contact update time is "
250 << (last_contact_update_time_.is_null() ?
251 std::string("null") :
252 gdata::util::FormatTimeAsString(last_contact_update_time_));
253 }
254
255 void GoogleContactStore::OnDownloadSuccess(
256 bool is_full_update,
257 const base::Time& update_start_time,
258 scoped_ptr<ScopedVector<Contact> > updated_contacts) {
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
260 VLOG(1) << "Got " << updated_contacts->size() << " contact(s) for "
261 << profile_->GetProfileName();
262 size_t num_updated_contacts = updated_contacts->size();
263 MergeContacts(is_full_update, updated_contacts.Pass());
264 last_successful_update_start_time_ = update_start_time;
265
266 if (is_full_update || num_updated_contacts > 0) {
267 FOR_EACH_OBSERVER(ContactStoreObserver,
268 observers_,
269 OnContactsUpdated(this));
270 }
271
272 if (db_) {
273 scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
274 for (ContactMap::const_iterator it = contacts_.begin();
275 it != contacts_.end(); ++it) {
276 contacts_to_save->push_back(it->second);
277 }
278 scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata);
279 metadata->set_last_update_start_time(update_start_time.ToInternalValue());
280
281 db_->SaveContacts(
282 contacts_to_save.Pass(),
283 metadata.Pass(),
284 is_full_update,
285 base::Bind(&GoogleContactStore::OnDatabaseContactsSaved,
286 weak_ptr_factory_.GetWeakPtr()));
287
288 // We'll schedule an update from OnDatabaseContactsSaved() after we're done
289 // writing to the database -- we don't want to modify the contacts while
290 // they're being used by the database.
291 } else {
292 ScheduleUpdate(true);
293 }
294 }
295
296 void GoogleContactStore::OnDownloadFailure() {
297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298 LOG(WARNING) << "Contacts download failed for " << profile_->GetProfileName();
299 ScheduleUpdate(false);
300 }
301
302 void GoogleContactStore::OnDatabaseInitialized(bool success) {
303 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
304 if (success) {
305 VLOG(1) << "Contact database initialized for "
306 << profile_->GetProfileName();
307 db_->LoadContacts(base::Bind(&GoogleContactStore::OnDatabaseContactsLoaded,
308 weak_ptr_factory_.GetWeakPtr()));
309 } else {
310 LOG(WARNING) << "Failed to initialize contact database for "
311 << profile_->GetProfileName();
312 // Limp along as best as we can: throw away the database and do an update,
313 // which will schedule further updates.
314 DestroyDatabase();
315 UpdateContacts();
316 }
317 }
318
319 void GoogleContactStore::OnDatabaseContactsLoaded(
320 bool success,
321 scoped_ptr<ScopedVector<Contact> > contacts,
322 scoped_ptr<UpdateMetadata> metadata) {
323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324 if (success) {
325 VLOG(1) << "Loaded " << contacts->size() << " contact(s) from database";
326 MergeContacts(true, contacts.Pass());
327 last_successful_update_start_time_ =
328 base::Time::FromInternalValue(metadata->last_update_start_time());
329
330 if (!contacts_.empty()) {
331 FOR_EACH_OBSERVER(ContactStoreObserver,
332 observers_,
333 OnContactsUpdated(this));
334 }
335 } else {
336 LOG(WARNING) << "Failed to load contacts from database";
337 }
338 UpdateContacts();
339 }
340
341 void GoogleContactStore::OnDatabaseContactsSaved(bool success) {
342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
343 if (!success)
344 LOG(WARNING) << "Failed to save contacts to database";
345
346 // We only update the database when we've successfully downloaded contacts, so
347 // report success to ScheduleUpdate() even if the database update failed.
348 ScheduleUpdate(true);
349 }
350
351 } // namespace contacts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698