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

Side by Side Diff: chrome/browser/chromeos/gdata/gdata_contacts_service.cc

Issue 10818017: contacts: Add GDataContactsService. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: s/struct/class/ in forward declaration (caught by clang) 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/gdata/gdata_contacts_service.h"
6
7 #include <cstring>
8 #include <string>
9 #include <map>
10 #include <utility>
11
12 #include "base/json/json_writer.h"
13 #include "base/logging.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/stl_util.h"
16 #include "base/values.h"
17 #include "chrome/browser/chromeos/contacts/contact.pb.h"
18 #include "chrome/browser/chromeos/gdata/gdata_operation_registry.h"
19 #include "chrome/browser/chromeos/gdata/gdata_operation_runner.h"
20 #include "chrome/browser/chromeos/gdata/gdata_operations.h"
21 #include "chrome/browser/chromeos/gdata/gdata_params.h"
22 #include "chrome/browser/chromeos/gdata/gdata_util.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "content/public/browser/browser_thread.h"
25
26 using content::BrowserThread;
27
28 namespace gdata {
29
30 namespace {
31
32 // Maximum number of profile photos that we'll download at once.
33 const int kMaxSimultaneousPhotoDownloads = 10;
34
35 // Field in the top-level object containing the contacts feed.
36 const char kFeedField[] = "feed";
37
38 // Field in the contacts feed containing a list of category information, along
39 // with fields within the dictionaries contained in the list and expected
40 // values.
41 const char kCategoryField[] = "category";
42 const char kCategorySchemeField[] = "scheme";
43 const char kCategorySchemeValue[] = "http://schemas.google.com/g/2005#kind";
44 const char kCategoryTermField[] = "term";
45 const char kCategoryTermValue[] =
46 "http://schemas.google.com/contact/2008#contact";
47
48 // Field in the contacts feed containing a list of contact entries.
49 const char kEntryField[] = "entry";
50
51 // Top-level fields in contact entries.
52 const char kIdField[] = "id.$t";
53 const char kDeletedField[] = "gd$deleted";
54 const char kFullNameField[] = "gd$name.gd$fullName.$t";
55 const char kGivenNameField[] = "gd$name.gd$givenName.$t";
56 const char kAdditionalNameField[] = "gd$name.gd$additionalName.$t";
57 const char kFamilyNameField[] = "gd$name.gd$familyName.$t";
58 const char kNamePrefixField[] = "gd$name.gd$namePrefix.$t";
59 const char kNameSuffixField[] = "gd$name.gd$nameSuffix.$t";
60 const char kEmailField[] = "gd$email";
61 const char kPhoneField[] = "gd$phoneNumber";
62 const char kPostalAddressField[] = "gd$structuredPostalAddress";
63 const char kInstantMessagingField[] = "gd$im";
64 const char kLinkField[] = "link";
65 const char kUpdatedField[] = "updated.$t";
66
67 // Fields in entries in the |kEmailField| list.
68 const char kEmailAddressField[] = "address";
69
70 // Fields in entries in the |kPhoneField| list.
71 const char kPhoneNumberField[] = "$t";
72
73 // Fields in entries in the |kPostalAddressField| list.
74 const char kPostalAddressFormattedField[] = "gd$formattedAddress.$t";
75
76 // Fields in entries in the |kInstantMessagingField| list.
77 const char kInstantMessagingAddressField[] = "address";
78 const char kInstantMessagingProtocolField[] = "protocol";
79 const char kInstantMessagingProtocolAimValue[] =
80 "http://schemas.google.com/g/2005#AIM";
81 const char kInstantMessagingProtocolMsnValue[] =
82 "http://schemas.google.com/g/2005#MSN";
83 const char kInstantMessagingProtocolYahooValue[] =
84 "http://schemas.google.com/g/2005#YAHOO";
85 const char kInstantMessagingProtocolSkypeValue[] =
86 "http://schemas.google.com/g/2005#SKYPE";
87 const char kInstantMessagingProtocolQqValue[] =
88 "http://schemas.google.com/g/2005#QQ";
89 const char kInstantMessagingProtocolGoogleTalkValue[] =
90 "http://schemas.google.com/g/2005#GOOGLE_TALK";
91 const char kInstantMessagingProtocolIcqValue[] =
92 "http://schemas.google.com/g/2005#ICQ";
93 const char kInstantMessagingProtocolJabberValue[] =
94 "http://schemas.google.com/g/2005#JABBER";
95
96 // Generic fields shared between address-like items (email, postal, etc.).
97 const char kAddressPrimaryField[] = "primary";
98 const char kAddressPrimaryTrueValue[] = "true";
99 const char kAddressRelField[] = "rel";
100 const char kAddressRelHomeValue[] = "http://schemas.google.com/g/2005#home";
101 const char kAddressRelWorkValue[] = "http://schemas.google.com/g/2005#work";
102 const char kAddressRelMobileValue[] = "http://schemas.google.com/g/2005#mobile";
103 const char kAddressLabelField[] = "label";
104
105 // Fields in entries in the |kLinkField| list.
106 const char kLinkHrefField[] = "href";
107 const char kLinkRelField[] = "rel";
108 const char kLinkETagField[] = "gd$etag";
109 const char kLinkRelPhotoValue[] =
110 "http://schemas.google.com/contacts/2008/rel#photo";
111
112 // Returns a string containing a pretty-printed JSON representation of |value|.
113 std::string PrettyPrintValue(const base::Value& value) {
114 std::string out;
115 base::JSONWriter::WriteWithOptions(
116 &value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &out);
117 return out;
118 }
119
120 // Returns whether an address is primary, given a dictionary representing a
121 // single address.
122 bool IsAddressPrimary(const DictionaryValue& address_dict) {
123 std::string primary;
124 address_dict.GetString(kAddressPrimaryField, &primary);
125 return primary == kAddressPrimaryTrueValue;
126 }
127
128 // Initializes an AddressType message given a dictionary representing a single
129 // address.
130 void InitAddressType(const DictionaryValue& address_dict,
131 contacts::Contact_AddressType* type) {
132 DCHECK(type);
133 type->Clear();
134
135 std::string rel;
136 address_dict.GetString(kAddressRelField, &rel);
137 if (rel == kAddressRelHomeValue)
138 type->set_relation(contacts::Contact_AddressType_Relation_HOME);
139 else if (rel == kAddressRelWorkValue)
140 type->set_relation(contacts::Contact_AddressType_Relation_WORK);
141 else if (rel == kAddressRelMobileValue)
142 type->set_relation(contacts::Contact_AddressType_Relation_MOBILE);
143 else
144 type->set_relation(contacts::Contact_AddressType_Relation_OTHER);
145
146 address_dict.GetString(kAddressLabelField, type->mutable_label());
147 }
148
149 // Maps the protocol from a dictionary representing a contact's IM address to a
150 // contacts::Contact_InstantMessagingAddress_Protocol value.
151 contacts::Contact_InstantMessagingAddress_Protocol
152 GetInstantMessagingProtocol(const DictionaryValue& im_dict) {
153 std::string protocol;
154 im_dict.GetString(kInstantMessagingProtocolField, &protocol);
155 if (protocol == kInstantMessagingProtocolAimValue)
156 return contacts::Contact_InstantMessagingAddress_Protocol_AIM;
157 else if (protocol == kInstantMessagingProtocolMsnValue)
158 return contacts::Contact_InstantMessagingAddress_Protocol_MSN;
159 else if (protocol == kInstantMessagingProtocolYahooValue)
160 return contacts::Contact_InstantMessagingAddress_Protocol_YAHOO;
161 else if (protocol == kInstantMessagingProtocolSkypeValue)
162 return contacts::Contact_InstantMessagingAddress_Protocol_SKYPE;
163 else if (protocol == kInstantMessagingProtocolQqValue)
164 return contacts::Contact_InstantMessagingAddress_Protocol_QQ;
165 else if (protocol == kInstantMessagingProtocolGoogleTalkValue)
166 return contacts::Contact_InstantMessagingAddress_Protocol_GOOGLE_TALK;
167 else if (protocol == kInstantMessagingProtocolIcqValue)
168 return contacts::Contact_InstantMessagingAddress_Protocol_ICQ;
169 else if (protocol == kInstantMessagingProtocolJabberValue)
170 return contacts::Contact_InstantMessagingAddress_Protocol_JABBER;
171 else
172 return contacts::Contact_InstantMessagingAddress_Protocol_OTHER;
173 }
174
175 // Gets the photo URL from a contact's dictionary (within the "entry" list).
176 // Returns an empty string if no photo was found.
177 std::string GetPhotoUrl(const DictionaryValue& dict) {
178 const ListValue* link_list = NULL;
179 if (!dict.GetList(kLinkField, &link_list))
180 return std::string();
181
182 for (size_t i = 0; i < link_list->GetSize(); ++i) {
183 DictionaryValue* link_dict = NULL;
184 if (!link_list->GetDictionary(i, &link_dict))
185 continue;
186
187 std::string rel;
188 if (!link_dict->GetString(kLinkRelField, &rel))
189 continue;
190 if (rel != kLinkRelPhotoValue)
191 continue;
192
193 // From https://goo.gl/7T6Od: "If a contact does not have a photo, then the
194 // photo link element has no gd:etag attribute."
195 std::string etag;
196 if (!link_dict->GetString(kLinkETagField, &etag))
197 continue;
198
199 std::string url;
200 if (link_dict->GetString(kLinkHrefField, &url))
201 return url;
202 }
203 return std::string();
204 }
205
206 // Fills a Contact's fields using an entry from a GData feed.
207 bool FillContactFromDictionary(const base::DictionaryValue& dict,
208 contacts::Contact* contact) {
209 DCHECK(contact);
210 contact->Clear();
211
212 if (!dict.GetString(kIdField, contact->mutable_provider_id()))
213 return false;
214
215 std::string updated;
216 if (dict.GetString(kUpdatedField, &updated)) {
217 base::Time update_time;
218 if (!util::GetTimeFromString(updated, &update_time)) {
219 LOG(WARNING) << "Unable to parse time \"" << updated << "\"";
220 return false;
221 }
222 contact->set_update_time(update_time.ToInternalValue());
223 }
224
225 const base::Value* deleted_value = NULL;
226 contact->set_deleted(dict.Get(kDeletedField, &deleted_value));
227 if (contact->deleted())
228 return true;
229
230 dict.GetString(kFullNameField, contact->mutable_full_name());
231 dict.GetString(kGivenNameField, contact->mutable_given_name());
232 dict.GetString(kAdditionalNameField, contact->mutable_additional_name());
233 dict.GetString(kFamilyNameField, contact->mutable_family_name());
234 dict.GetString(kNamePrefixField, contact->mutable_name_prefix());
235 dict.GetString(kNameSuffixField, contact->mutable_name_suffix());
236
237 const ListValue* email_list = NULL;
238 if (dict.GetList(kEmailField, &email_list)) {
239 for (size_t i = 0; i < email_list->GetSize(); ++i) {
240 DictionaryValue* email_dict = NULL;
241 if (!email_list->GetDictionary(i, &email_dict))
242 return false;
243
244 contacts::Contact_EmailAddress* email = contact->add_email_addresses();
245 if (!email_dict->GetString(kEmailAddressField, email->mutable_address()))
246 return false;
247 email->set_primary(IsAddressPrimary(*email_dict));
248 InitAddressType(*email_dict, email->mutable_type());
249 }
250 }
251
252 const ListValue* phone_list = NULL;
253 if (dict.GetList(kPhoneField, &phone_list)) {
254 for (size_t i = 0; i < phone_list->GetSize(); ++i) {
255 DictionaryValue* phone_dict = NULL;
256 if (!phone_list->GetDictionary(i, &phone_dict))
257 return false;
258
259 contacts::Contact_PhoneNumber* phone = contact->add_phone_numbers();
260 if (!phone_dict->GetString(kPhoneNumberField, phone->mutable_number()))
261 return false;
262 phone->set_primary(IsAddressPrimary(*phone_dict));
263 InitAddressType(*phone_dict, phone->mutable_type());
264 }
265 }
266
267 const ListValue* address_list = NULL;
268 if (dict.GetList(kPostalAddressField, &address_list)) {
269 for (size_t i = 0; i < address_list->GetSize(); ++i) {
270 DictionaryValue* address_dict = NULL;
271 if (!address_list->GetDictionary(i, &address_dict))
272 return false;
273
274 contacts::Contact_PostalAddress* address =
275 contact->add_postal_addresses();
276 if (!address_dict->GetString(kPostalAddressFormattedField,
277 address->mutable_address())) {
278 return false;
279 }
280 address->set_primary(IsAddressPrimary(*address_dict));
281 InitAddressType(*address_dict, address->mutable_type());
282 }
283 }
284
285 const ListValue* im_list = NULL;
286 if (dict.GetList(kInstantMessagingField, &im_list)) {
287 for (size_t i = 0; i < im_list->GetSize(); ++i) {
288 DictionaryValue* im_dict = NULL;
289 if (!im_list->GetDictionary(i, &im_dict))
290 return false;
291
292 contacts::Contact_InstantMessagingAddress* im =
293 contact->add_instant_messaging_addresses();
294 if (!im_dict->GetString(kInstantMessagingAddressField,
295 im->mutable_address())) {
296 return false;
297 }
298 im->set_primary(IsAddressPrimary(*im_dict));
299 InitAddressType(*im_dict, im->mutable_type());
300 im->set_protocol(GetInstantMessagingProtocol(*im_dict));
301 }
302 }
303
304 return true;
305 }
306
307 } // namespace
308
309 // This class handles a single request to download all of a user's contacts.
310 //
311 // First, the contacts feed is downloaded via GetContactsOperation and parsed.
312 // Individual contacts::Contact objects are created using the data from the
313 // feed. Next, GetContactPhotoOperations are created and used to start
314 // downloading contacts' photos in parallel. When all photos have been
315 // downloaded, the contacts are passed to the passed-in callback.
316 class GDataContactsService::DownloadContactsRequest
317 : public base::SupportsWeakPtr<DownloadContactsRequest> {
318 public:
319 DownloadContactsRequest(GDataContactsService* service,
320 Profile* profile,
321 GDataOperationRunner* runner,
322 SuccessCallback success_callback,
323 FailureCallback failure_callback,
324 const base::Time& min_update_time,
325 int max_simultaneous_photo_downloads)
326 : service_(service),
327 profile_(profile),
328 runner_(runner),
329 success_callback_(success_callback),
330 failure_callback_(failure_callback),
331 min_update_time_(min_update_time),
332 contacts_(new ScopedVector<contacts::Contact>),
333 max_simultaneous_photo_downloads_(max_simultaneous_photo_downloads),
334 num_in_progress_photo_downloads_(0),
335 photo_download_failed_(false) {
336 DCHECK(service_);
337 DCHECK(profile_);
338 DCHECK(runner_);
339 }
340
341 ~DownloadContactsRequest() {
342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
343 service_ = NULL;
344 profile_ = NULL;
345 runner_ = NULL;
346 }
347
348 // Issues the initial request to download the contact feed.
349 void Run() {
350 GetContactsOperation* operation =
351 new GetContactsOperation(
352 runner_->operation_registry(),
353 profile_,
354 min_update_time_,
355 base::Bind(&DownloadContactsRequest::HandleFeedData,
356 base::Unretained(this)));
357 if (!service_->feed_url_for_testing_.is_empty())
358 operation->set_feed_url_for_testing(service_->feed_url_for_testing_);
359
360 runner_->StartOperationWithRetry(operation);
361 }
362
363 private:
364 // Callback for GetContactsOperation calls.
365 void HandleFeedData(GDataErrorCode error,
366 scoped_ptr<base::Value> feed_data) {
367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
368 if (error != HTTP_SUCCESS) {
369 LOG(WARNING) << "Got error " << error << " while downloading contacts";
370 failure_callback_.Run();
371 service_->OnRequestComplete(this);
372 return;
373 }
374
375 VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get()));
376 if (!ProcessFeedData(*feed_data.get())) {
377 LOG(WARNING) << "Unable to process feed data";
378 failure_callback_.Run();
379 service_->OnRequestComplete(this);
380 return;
381 }
382
383 CheckCompletion();
384 }
385
386 // Processes the raw contacts feed from |feed_data| and fills |contacts_|.
387 // Returns true on success.
388 bool ProcessFeedData(const base::Value& feed_data) {
389 const DictionaryValue* toplevel_dict = NULL;
390 if (!feed_data.GetAsDictionary(&toplevel_dict)) {
391 LOG(WARNING) << "Top-level object is not a dictionary";
392 return false;
393 }
394
395 const DictionaryValue* feed_dict = NULL;
396 if (!toplevel_dict->GetDictionary(kFeedField, &feed_dict)) {
397 LOG(WARNING) << "Feed dictionary missing";
398 return false;
399 }
400
401 // Check the category field to confirm that this is actually a contact feed.
402 const ListValue* category_list = NULL;
403 if (!feed_dict->GetList(kCategoryField, &category_list)) {
404 LOG(WARNING) << "Category list missing";
405 return false;
406 }
407 DictionaryValue* category_dict = NULL;
408 if (!category_list->GetSize() == 1 ||
409 !category_list->GetDictionary(0, &category_dict)) {
410 LOG(WARNING) << "Unable to get dictionary from category list of size "
411 << category_list->GetSize();
412 return false;
413 }
414 std::string category_scheme, category_term;
415 if (!category_dict->GetString(kCategorySchemeField, &category_scheme) ||
416 !category_dict->GetString(kCategoryTermField, &category_term) ||
417 category_scheme != kCategorySchemeValue ||
418 category_term != kCategoryTermValue) {
419 LOG(WARNING) << "Unexpected category (scheme was \"" << category_scheme
420 << "\", term was \"" << category_term << "\")";
421 return false;
422 }
423
424 // A missing entry list means no entries (maybe we're doing an incremental
425 // update and nothing has changed).
426 const ListValue* entry_list = NULL;
427 if (!feed_dict->GetList(kEntryField, &entry_list))
428 return true;
429
430 contacts_needing_photo_downloads_.reserve(entry_list->GetSize());
431
432 for (ListValue::const_iterator entry_it = entry_list->begin();
433 entry_it != entry_list->end(); ++entry_it) {
434 const size_t index = (entry_it - entry_list->begin());
435 const DictionaryValue* contact_dict = NULL;
436 if (!(*entry_it)->GetAsDictionary(&contact_dict)) {
437 LOG(WARNING) << "Entry " << index << " isn't a dictionary";
438 return false;
439 }
440
441 scoped_ptr<contacts::Contact> contact(new contacts::Contact);
442 if (!FillContactFromDictionary(*contact_dict, contact.get())) {
443 LOG(WARNING) << "Unable to fill entry " << index;
444 return false;
445 }
446
447 VLOG(1) << "Got contact " << index << ":"
448 << " id=" << contact->provider_id()
449 << " full_name=\"" << contact->full_name() << "\""
450 << " update_time=" << contact->update_time();
451
452 std::string photo_url = GetPhotoUrl(*contact_dict);
453 if (!photo_url.empty()) {
454 if (!service_->rewrite_photo_url_callback_for_testing_.is_null()) {
455 photo_url =
456 service_->rewrite_photo_url_callback_for_testing_.Run(photo_url);
457 }
458 contact_photo_urls_[contact.get()] = photo_url;
459 contacts_needing_photo_downloads_.push_back(contact.get());
460 }
461
462 contacts_->push_back(contact.release());
463 }
464
465 return true;
466 }
467
468 // If we're done downloading photos, invokes a callback and deletes |this|.
469 // Otherwise, starts one or more downloads of URLs from
470 // |contacts_needing_photo_downloads_|.
471 void CheckCompletion() {
472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
473 if (contacts_needing_photo_downloads_.empty() &&
474 num_in_progress_photo_downloads_ == 0) {
475 VLOG(1) << "Done downloading photos; invoking callback";
476 if (photo_download_failed_)
477 failure_callback_.Run();
478 else
479 success_callback_.Run(contacts_.Pass());
480 service_->OnRequestComplete(this);
481 return;
482 }
483
484 while (!contacts_needing_photo_downloads_.empty() &&
485 (num_in_progress_photo_downloads_ <
486 max_simultaneous_photo_downloads_)) {
487 contacts::Contact* contact = contacts_needing_photo_downloads_.back();
488 contacts_needing_photo_downloads_.pop_back();
489 DCHECK(contact_photo_urls_.count(contact));
490 std::string url = contact_photo_urls_[contact];
491
492 VLOG(1) << "Starting download of photo " << url << " for "
493 << contact->provider_id();
494 runner_->StartOperationWithRetry(
495 new GetContactPhotoOperation(
496 runner_->operation_registry(),
497 profile_,
498 GURL(url),
499 base::Bind(&DownloadContactsRequest::HandlePhotoData,
500 AsWeakPtr(), contact)));
501 num_in_progress_photo_downloads_++;
502 }
503 }
504
505 // Callback for GetContactPhotoOperation calls. Updates the associated
506 // Contact and checks for completion.
507 void HandlePhotoData(contacts::Contact* contact,
508 GDataErrorCode error,
509 scoped_ptr<std::string> download_data) {
510 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
511 VLOG(1) << "Got photo data for " << contact->provider_id()
512 << " (error=" << error << " size=" << download_data->size() << ")";
513 num_in_progress_photo_downloads_--;
514
515 if (error != HTTP_SUCCESS) {
516 LOG(WARNING) << "Got error " << error << " while downloading photo "
517 << "for " << contact->provider_id();
518 // TODO(derat): Retry several times for temporary failures?
519 photo_download_failed_ = true;
520 // Make sure we don't start any more downloads.
521 contacts_needing_photo_downloads_.clear();
522 CheckCompletion();
523 return;
524 }
525
526 contact->set_raw_untrusted_photo(*download_data);
527 CheckCompletion();
528 }
529
530 private:
531 typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls;
532
533 GDataContactsService* service_; // not owned
534 Profile* profile_; // not owned
535 GDataOperationRunner* runner_; // not owned
536
537 SuccessCallback success_callback_;
538 FailureCallback failure_callback_;
539
540 base::Time min_update_time_;
541
542 scoped_ptr<ScopedVector<contacts::Contact> > contacts_;
543
544 // Map from a contact to the URL at which its photo is located.
545 // Contacts without photos do not appear in this map.
546 ContactPhotoUrls contact_photo_urls_;
547
548 // Contacts that have photos that we still need to start downloading.
549 // When we start a download, the contact is removed from this list.
550 std::vector<contacts::Contact*> contacts_needing_photo_downloads_;
551
552 // Maximum number of photos we'll try to download at once.
553 int max_simultaneous_photo_downloads_;
554
555 // Number of in-progress photo downloads.
556 int num_in_progress_photo_downloads_;
557
558 // Did we encounter a fatal error while downloading a photo?
559 bool photo_download_failed_;
560
561 DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest);
562 };
563
564 GDataContactsService::GDataContactsService(Profile* profile)
565 : profile_(profile),
566 runner_(new GDataOperationRunner(profile)),
567 max_simultaneous_photo_downloads_(kMaxSimultaneousPhotoDownloads) {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
569 DCHECK(profile_);
570 }
571
572 GDataContactsService::~GDataContactsService() {
573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
574 runner_->CancelAll();
575 STLDeleteContainerPointers(requests_.begin(), requests_.end());
576 requests_.clear();
577 }
578
579 GDataAuthService* GDataContactsService::auth_service_for_testing() {
580 return runner_->auth_service();
581 }
582
583 void GDataContactsService::Initialize() {
584 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
585 runner_->Initialize();
586 }
587
588 void GDataContactsService::DownloadContacts(SuccessCallback success_callback,
589 FailureCallback failure_callback,
590 const base::Time& min_update_time) {
591 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
592 DownloadContactsRequest* request =
593 new DownloadContactsRequest(this,
594 profile_,
595 runner_.get(),
596 success_callback,
597 failure_callback,
598 min_update_time,
599 max_simultaneous_photo_downloads_);
600 VLOG(1) << "Starting contacts download with request " << request;
601 requests_.insert(request);
602 request->Run();
603 }
604
605 void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) {
606 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
607 DCHECK(request);
608 VLOG(1) << "Download request " << request << " complete";
609 requests_.erase(request);
610 delete request;
611 }
612
613 } // namespace contacts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698