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

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: minor updates 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");
satorux1 2012/08/02 22:21:25 I'm old fashioned enough to worry about file names
Daniel Erat 2012/08/02 23:23:18 Hmm. It looks like using spaces is more common th
satorux1 2012/08/03 00:11:10 Ah then, spaces are fine.
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 }
84
85 GoogleContactStore::~GoogleContactStore() {
86 DestroyDatabase();
87 }
88
89 void GoogleContactStore::Init() {
90 FilePath db_path = profile_->GetPath().Append(kDatabaseDirectoryName);
91 VLOG(1) << "Initializing contact database \"" << db_path.value() << "\" for "
92 << profile_->GetProfileName();
93 db_->Init(db_path,
94 base::Bind(&GoogleContactStore::OnDatabaseInitialized,
95 AsWeakPtr()));
96 }
97
98 void GoogleContactStore::AppendContacts(ContactPointers* contacts_out) {
99 DCHECK(contacts_out);
100 for (ContactMap::const_iterator it = contacts_.begin();
101 it != contacts_.end(); ++it) {
102 if (!it->second->deleted())
103 contacts_out->push_back(it->second);
104 }
105 }
106
107 const Contact* GoogleContactStore::GetContactByProviderId(
108 const std::string& provider_id) {
109 ContactMap::const_iterator it = contacts_.find(provider_id);
110 return (it != contacts_.end() && !it->second->deleted()) ? it->second : NULL;
111 }
112
113 void GoogleContactStore::AddObserver(ContactStoreObserver* observer) {
114 DCHECK(observer);
115 observers_.AddObserver(observer);
116 }
117
118 void GoogleContactStore::RemoveObserver(ContactStoreObserver* observer) {
119 DCHECK(observer);
120 observers_.RemoveObserver(observer);
121 }
122
123 base::Time GoogleContactStore::GetCurrentTime() const {
124 return !current_time_for_testing_.is_null() ?
125 current_time_for_testing_ :
126 base::Time::Now();
127 }
128
129 void GoogleContactStore::DestroyDatabase() {
130 if (db_) {
131 db_->DestroyOnUIThread();
132 db_ = NULL;
133 }
134 }
135
136 void GoogleContactStore::UpdateContacts() {
137 base::Time min_update_time;
138 base::TimeDelta time_since_last_update =
139 last_successful_update_start_time_.is_null() ?
140 base::TimeDelta() :
141 GetCurrentTime() - last_successful_update_start_time_;
142
143 if (!last_contact_update_time_.is_null() &&
144 time_since_last_update <
145 base::TimeDelta::FromDays(kForceFullUpdateDays)) {
146 // TODO(derat): I'm adding one millisecond to the last update time here as I
147 // don't want to re-download the same most-recently-updated contact each
148 // time, but what happens if within the same millisecond, contact A is
149 // updated, we do a sync, and then contact B is updated? I'm probably being
150 // overly paranoid about this.
151 min_update_time =
152 last_contact_update_time_ + base::TimeDelta::FromMilliseconds(1);
153 }
154 if (min_update_time.is_null()) {
155 VLOG(1) << "Downloading all contacts for " << profile_->GetProfileName();
156 } else {
157 VLOG(1) << "Downloading contacts updated since "
158 << gdata::util::FormatTimeAsString(min_update_time) << " for "
159 << profile_->GetProfileName();
160 }
161
162 gdata::GDataContactsServiceInterface* service =
163 gdata_service_for_testing_.get() ?
164 gdata_service_for_testing_.get() :
165 gdata::GDataSystemServiceFactory::GetForProfile(profile_)->
166 contacts_service();
167 DCHECK(service);
168 service->DownloadContacts(
169 base::Bind(&GoogleContactStore::OnDownloadSuccess,
170 AsWeakPtr(),
171 min_update_time.is_null(),
172 GetCurrentTime()),
173 base::Bind(&GoogleContactStore::OnDownloadFailure, AsWeakPtr()),
174 min_update_time);
175 }
176
177 void GoogleContactStore::ScheduleUpdate(bool last_update_was_successful) {
178 base::TimeDelta delay;
179 if (last_update_was_successful) {
180 delay = base::TimeDelta::FromSeconds(kUpdateIntervalSec);
181 update_delay_on_next_failure_ =
182 base::TimeDelta::FromSeconds(kUpdateFailureInitialRetrySec);
183 } else {
184 delay = update_delay_on_next_failure_;
185 update_delay_on_next_failure_ = std::min(
186 update_delay_on_next_failure_ * kUpdateFailureBackoffFactor,
187 base::TimeDelta::FromSeconds(kUpdateIntervalSec));
188 }
189 VLOG(1) << "Scheduling update of " << profile_->GetProfileName()
190 << " in " << delay.InSeconds() << " second(s)";
191 update_timer_.Start(
192 FROM_HERE, delay, this, &GoogleContactStore::UpdateContacts);
193 }
194
195 void GoogleContactStore::MergeContacts(
196 bool is_full_update,
197 scoped_ptr<ScopedVector<Contact> > updated_contacts) {
198 if (is_full_update)
199 contacts_.clear();
200
201 for (ScopedVector<Contact>::iterator it = updated_contacts->begin();
202 it != updated_contacts->end(); ++it) {
203 Contact* contact = *it;
204 VLOG(1) << "Updating " << contact->provider_id();
205 ContactMap::iterator map_it = contacts_.find(contact->provider_id());
206 if (map_it == contacts_.end()) {
207 contacts_[contact->provider_id()] = contact;
208 } else {
209 delete map_it->second;
210 map_it->second = contact;
211 }
212 }
213
214 // Make sure that the Contact objects won't be destroyed when
215 // |updated_contacts| is destroyed.
216 size_t num_updated_contacts = updated_contacts->size();
217 updated_contacts->weak_clear();
satorux1 2012/08/02 22:21:25 I was a bit confused. Here, weak_clear() is used,
Daniel Erat 2012/08/02 23:23:18 ScopedVector doesn't offer pop_back() (and if it d
satorux1 2012/08/03 00:11:10 I see. Then, we should keep this way.
218
219 if (is_full_update || num_updated_contacts > 0) {
220 // Find the latest update time.
221 last_contact_update_time_ = base::Time();
222 for (ContactMap::const_iterator it = contacts_.begin();
223 it != contacts_.end(); ++it) {
224 const Contact* contact = it->second;
225 base::Time update_time =
226 base::Time::FromInternalValue(contact->update_time());
227
228 if (!update_time.is_null() &&
229 (last_contact_update_time_.is_null() ||
230 last_contact_update_time_ < update_time)) {
231 last_contact_update_time_ = update_time;
232 }
233 }
234 }
235 VLOG(1) << "Last contact update time is "
236 << (last_contact_update_time_.is_null() ?
237 std::string("null") :
238 gdata::util::FormatTimeAsString(last_contact_update_time_));
239 }
240
241 void GoogleContactStore::OnDownloadSuccess(
242 bool is_full_update,
243 const base::Time& update_start_time,
244 scoped_ptr<ScopedVector<Contact> > updated_contacts) {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
246 VLOG(1) << "Got " << updated_contacts->size() << " contact(s) for "
247 << profile_->GetProfileName();
248 size_t num_updated_contacts = updated_contacts->size();
249 MergeContacts(is_full_update, updated_contacts.Pass());
250 last_successful_update_start_time_ = update_start_time;
251
252 if (is_full_update || num_updated_contacts > 0) {
253 FOR_EACH_OBSERVER(ContactStoreObserver,
254 observers_,
255 OnContactsUpdated(this));
256 }
257
258 if (db_) {
259 scoped_ptr<ContactPointers> contacts_to_save(new ContactPointers);
260 for (ContactMap::const_iterator it = contacts_.begin();
261 it != contacts_.end(); ++it) {
262 contacts_to_save->push_back(it->second);
263 }
264 scoped_ptr<UpdateMetadata> metadata(new UpdateMetadata);
265 metadata->set_last_update_start_time(update_start_time.ToInternalValue());
266
267 db_->SaveContacts(
268 contacts_to_save.Pass(),
269 metadata.Pass(),
270 is_full_update,
271 base::Bind(&GoogleContactStore::OnDatabaseContactsSaved, AsWeakPtr()));
272
273 // We'll schedule an update from OnDatabaseContactsSaved() after we're done
274 // writing to the database -- we don't want to modify the contacts while
275 // they're being used by the database.
276 } else {
277 ScheduleUpdate(true);
278 }
279 }
280
281 void GoogleContactStore::OnDownloadFailure() {
282 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
283 LOG(WARNING) << "Contacts download failed for " << profile_->GetProfileName();
284 ScheduleUpdate(false);
285 }
286
287 void GoogleContactStore::OnDatabaseInitialized(bool success) {
288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
289 if (success) {
290 VLOG(1) << "Contact database initialized for "
291 << profile_->GetProfileName();
292 db_->LoadContacts(base::Bind(&GoogleContactStore::OnDatabaseContactsLoaded,
293 AsWeakPtr()));
294 } else {
295 LOG(WARNING) << "Failed to initialize contact database for "
296 << profile_->GetProfileName();
297 // Limp along as best as we can: throw away the database and do an update,
298 // which will schedule further updates.
299 DestroyDatabase();
300 UpdateContacts();
301 }
302 }
303
304 void GoogleContactStore::OnDatabaseContactsLoaded(
305 bool success,
306 scoped_ptr<ScopedVector<Contact> > contacts,
307 scoped_ptr<UpdateMetadata> metadata) {
satorux1 2012/08/02 22:21:25 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::U
Daniel Erat 2012/08/02 23:23:18 Yep, also noticed this and added it locally. :-)
308 if (success) {
309 VLOG(1) << "Loaded " << contacts->size() << " contact(s) from database";
310 MergeContacts(true, contacts.Pass());
311 last_successful_update_start_time_ =
312 base::Time::FromInternalValue(metadata->last_update_start_time());
313
314 if (!contacts_.empty()) {
315 FOR_EACH_OBSERVER(ContactStoreObserver,
316 observers_,
317 OnContactsUpdated(this));
318 }
319 } else {
320 LOG(WARNING) << "Failed to load contacts from database";
321 }
322 UpdateContacts();
323 }
324
325 void GoogleContactStore::OnDatabaseContactsSaved(bool success) {
satorux1 2012/08/02 22:21:25 ditto
Daniel Erat 2012/08/02 23:23:18 Done.
326 if (!success)
327 LOG(WARNING) << "Failed to save contacts to database";
328
329 // We only update the database when we've successfully downloaded contacts, so
330 // report success to ScheduleUpdate() even if the database update failed.
331 ScheduleUpdate(true);
332 }
333
334 } // namespace contacts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698