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

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: minor cleanup Created 8 years, 5 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/stl_util.h"
15 #include "base/values.h"
16 #include "chrome/browser/chromeos/contacts/contact.h"
17 #include "chrome/browser/chromeos/gdata/gdata_operation_registry.h"
18 #include "chrome/browser/chromeos/gdata/gdata_operation_runner.h"
19 #include "chrome/browser/chromeos/gdata/gdata_operations.h"
20 #include "chrome/browser/chromeos/gdata/gdata_params.h"
21 #include "chrome/browser/chromeos/gdata/gdata_util.h"
22 #include "chrome/browser/image_decoder.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 a AddressType struct given a dictionary representing a single
129 // address.
130 void InitAddressType(const DictionaryValue& address_dict,
131 contacts::Contact::AddressType* type) {
132 std::string rel;
133 address_dict.GetString(kAddressRelField, &rel);
134 if (rel == kAddressRelHomeValue)
135 type->relation = contacts::Contact::AddressType::RELATION_HOME;
136 else if (rel == kAddressRelWorkValue)
137 type->relation = contacts::Contact::AddressType::RELATION_WORK;
138 else if (rel == kAddressRelMobileValue)
139 type->relation = contacts::Contact::AddressType::RELATION_MOBILE;
140 else
141 type->relation = contacts::Contact::AddressType::RELATION_OTHER;
142
143 address_dict.GetString(kAddressLabelField, &(type->label));
144 }
145
146 // Maps the protocol from a dictionary representing a contact's IM address to a
147 // contacts::Contact::InstantMessagingAddress::Protocol value.
148 contacts::Contact::InstantMessagingAddress::Protocol
149 GetInstantMessagingProtocol(const DictionaryValue& im_dict) {
150 std::string protocol;
151 im_dict.GetString(kInstantMessagingProtocolField, &protocol);
152 if (protocol == kInstantMessagingProtocolAimValue)
153 return contacts::Contact::InstantMessagingAddress::PROTOCOL_AIM;
154 else if (protocol == kInstantMessagingProtocolMsnValue)
155 return contacts::Contact::InstantMessagingAddress::PROTOCOL_MSN;
156 else if (protocol == kInstantMessagingProtocolYahooValue)
157 return contacts::Contact::InstantMessagingAddress::PROTOCOL_YAHOO;
158 else if (protocol == kInstantMessagingProtocolSkypeValue)
159 return contacts::Contact::InstantMessagingAddress::PROTOCOL_SKYPE;
160 else if (protocol == kInstantMessagingProtocolQqValue)
161 return contacts::Contact::InstantMessagingAddress::PROTOCOL_QQ;
162 else if (protocol == kInstantMessagingProtocolGoogleTalkValue)
163 return contacts::Contact::InstantMessagingAddress::PROTOCOL_GOOGLE_TALK;
164 else if (protocol == kInstantMessagingProtocolIcqValue)
165 return contacts::Contact::InstantMessagingAddress::PROTOCOL_ICQ;
166 else if (protocol == kInstantMessagingProtocolJabberValue)
167 return contacts::Contact::InstantMessagingAddress::PROTOCOL_JABBER;
168 else
169 return contacts::Contact::InstantMessagingAddress::PROTOCOL_OTHER;
170 }
171
172 // Assigns the photo URL from a contact's dictionary (within the "entry" list)
173 // to |url|. Returns true on success.
174 bool GetPhotoUrl(const DictionaryValue& dict, std::string* url) {
satorux1 2012/07/24 18:25:51 it may be slightly simpler to return an empty stri
Daniel Erat 2012/07/24 20:37:21 Done.
175 DCHECK(url);
176 ListValue* link_list = NULL;
177 if (!dict.GetList(kLinkField, &link_list))
178 return false;
179
180 for (size_t i = 0; i < link_list->GetSize(); ++i) {
181 DictionaryValue* link_dict = NULL;
182 if (!link_list->GetDictionary(i, &link_dict))
183 continue;
184
185 std::string rel;
186 if (!link_dict->GetString(kLinkRelField, &rel))
187 continue;
188 if (rel != kLinkRelPhotoValue)
189 continue;
190
191 // From https://goo.gl/7T6Od: "If a contact does not have a photo, then the
192 // photo link element has no gd:etag attribute."
193 std::string etag;
194 if (!link_dict->GetString(kLinkETagField, &etag))
195 continue;
196
197 if (link_dict->GetString(kLinkHrefField, url))
198 return true;
199 }
200 return false;
201 }
202
203 // Fills a Contact's fields using an entry from a GData feed.
204 bool FillContactFromDictionary(const base::DictionaryValue& dict,
satorux1 2012/07/24 18:25:51 this function is lengthy. Would it make sense to u
Daniel Erat 2012/07/24 20:37:21 Heh, see my very old, abandoned change at https://
205 contacts::Contact* contact) {
206 DCHECK(contact);
207 if (!dict.GetString(kIdField, &(contact->provider_id)))
208 return false;
209
210 std::string updated;
211 if (dict.GetString(kUpdatedField, &updated)) {
212 if (!util::GetTimeFromString(updated, &(contact->update_time))) {
213 LOG(WARNING) << "Unable to parse time \"" << updated << "\"";
214 return false;
215 }
216 }
217
218 base::Value* deleted_value = NULL;
219 contact->deleted = dict.Get(kDeletedField, &deleted_value);
220 if (contact->deleted)
221 return true;
222
223 dict.GetString(kFullNameField, &(contact->full_name));
224 dict.GetString(kGivenNameField, &(contact->given_name));
225 dict.GetString(kAdditionalNameField, &(contact->additional_name));
226 dict.GetString(kFamilyNameField, &(contact->family_name));
227 dict.GetString(kNamePrefixField, &(contact->name_prefix));
228 dict.GetString(kNameSuffixField, &(contact->name_suffix));
229
230 ListValue* email_list = NULL;
231 if (dict.GetList(kEmailField, &email_list)) {
232 for (size_t i = 0; i < email_list->GetSize(); ++i) {
233 DictionaryValue* email_dict = NULL;
234 if (!email_list->GetDictionary(i, &email_dict))
235 return false;
236
237 contacts::Contact::EmailAddress email;
238 if (!email_dict->GetString(kEmailAddressField, &email.address))
239 return false;
240
241 email.primary = IsAddressPrimary(*email_dict);
242 InitAddressType(*email_dict, &email.type);
243 contact->email_addresses.push_back(email);
244 }
245 }
246
247 ListValue* phone_list = NULL;
248 if (dict.GetList(kPhoneField, &phone_list)) {
249 for (size_t i = 0; i < phone_list->GetSize(); ++i) {
250 DictionaryValue* phone_dict = NULL;
251 if (!phone_list->GetDictionary(i, &phone_dict))
252 return false;
253
254 contacts::Contact::PhoneNumber phone;
255 if (!phone_dict->GetString(kPhoneNumberField, &phone.number))
256 return false;
257
258 phone.primary = IsAddressPrimary(*phone_dict);
259 InitAddressType(*phone_dict, &phone.type);
260 contact->phone_numbers.push_back(phone);
261 }
262 }
263
264 ListValue* address_list = NULL;
265 if (dict.GetList(kPostalAddressField, &address_list)) {
266 for (size_t i = 0; i < address_list->GetSize(); ++i) {
267 DictionaryValue* address_dict = NULL;
268 if (!address_list->GetDictionary(i, &address_dict))
269 return false;
270
271 contacts::Contact::PostalAddress address;
272 if (!address_dict->GetString(kPostalAddressFormattedField,
273 &address.address)) {
274 return false;
275 }
276 address.primary = IsAddressPrimary(*address_dict);
277 InitAddressType(*address_dict, &address.type);
278 contact->postal_addresses.push_back(address);
279 }
280 }
281
282 ListValue* im_list = NULL;
283 if (dict.GetList(kInstantMessagingField, &im_list)) {
284 for (size_t i = 0; i < im_list->GetSize(); ++i) {
285 DictionaryValue* im_dict = NULL;
286 if (!im_list->GetDictionary(i, &im_dict))
287 return false;
288
289 contacts::Contact::InstantMessagingAddress im;
290 if (!im_dict->GetString(kInstantMessagingAddressField, &im.address))
291 return false;
292 im.primary = IsAddressPrimary(*im_dict);
293 InitAddressType(*im_dict, &im.type);
294 im.protocol = GetInstantMessagingProtocol(*im_dict);
295 contact->instant_messaging_addresses.push_back(im);
296 }
297 }
298
299 return true;
300 }
301
302 } // namespace
303
304 class GDataContactsService::DownloadContactsRequest
305 : public ImageDecoder::Delegate {
306 public:
307 DownloadContactsRequest(GDataContactsService* service,
308 Profile* profile,
309 GDataOperationRunner* runner,
310 SuccessCallback success_callback,
311 FailureCallback failure_callback,
312 const base::Time& min_update_time,
313 int max_simultaneous_photo_downloads)
314 : service_(service),
315 profile_(profile),
316 runner_(runner),
317 success_callback_(success_callback),
318 failure_callback_(failure_callback),
319 min_update_time_(min_update_time),
320 contacts_(new ScopedVector<contacts::Contact>),
321 max_simultaneous_photo_downloads_(max_simultaneous_photo_downloads),
322 num_in_progress_photo_downloads_(0),
323 photo_download_failed_(false) {
324 DCHECK(service_);
325 DCHECK(profile_);
326 DCHECK(runner_);
327 }
328
329 ~DownloadContactsRequest() {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
331 // Abandon any outstanding image decode requests.
332 for (ImageDecoderContactMap::iterator it =
333 in_progress_photo_decodes_.begin();
334 it != in_progress_photo_decodes_.end(); ++it) {
335 it->first->set_delegate(NULL);
336 }
337 service_ = NULL;
338 profile_ = NULL;
339 runner_ = NULL;
340 }
341
342 void Run() {
343 GetContactsOperation* operation =
344 new GetContactsOperation(
345 runner_->operation_registry(),
346 profile_,
347 min_update_time_,
348 base::Bind(&DownloadContactsRequest::HandleFeedData,
349 base::Unretained(this)));
350 if (!service_->feed_url_for_testing_.is_empty())
351 operation->set_feed_url_for_testing(service_->feed_url_for_testing_);
352
353 runner_->StartOperationWithRetry(operation);
354 }
355
356 // ImageDecoder::Delegate overrides:
357 virtual void OnImageDecoded(const ImageDecoder* decoder,
358 const SkBitmap& decoded_image) OVERRIDE {
359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
360 ImageDecoderContactMap::iterator it =
361 in_progress_photo_decodes_.find(const_cast<ImageDecoder*>(decoder));
362 CHECK(it != in_progress_photo_decodes_.end());
363 contacts::Contact* contact = it->second;
364 VLOG(1) << "Downloaded and decoded " << decoded_image.width() << "x"
365 << decoded_image.height() << " photo for " << contact->provider_id;
366 contact->photo = decoded_image;
367 in_progress_photo_decodes_.erase(it);
368 CheckCompletion();
369 }
370
371 virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
373 ImageDecoderContactMap::iterator it =
374 in_progress_photo_decodes_.find(const_cast<ImageDecoder*>(decoder));
375 CHECK(it != in_progress_photo_decodes_.end());
376 contacts::Contact* contact = it->second;
377 LOG(WARNING) << "Failed to decode image for contact "
378 << contact->provider_id;
379 // Leave the photo blank but don't abort the entire download request.
380 in_progress_photo_decodes_.erase(it);
381 CheckCompletion();
382 }
383
384 private:
385 // Callback for GetContactsOperation calls.
386 void HandleFeedData(GDataErrorCode error,
387 scoped_ptr<base::Value> feed_data) {
388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
389 if (error != HTTP_SUCCESS) {
390 LOG(WARNING) << "Got error " << error << " while downloading contacts";
391 failure_callback_.Run();
392 service_->OnRequestComplete(this);
393 return;
394 }
395
396 VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get()));
397 if (!ProcessFeedData(*feed_data.get())) {
398 LOG(WARNING) << "Unable to process feed data";
399 failure_callback_.Run();
400 service_->OnRequestComplete(this);
401 return;
402 }
403
404 CheckCompletion();
405 }
406
407 // Processes the raw contacts feed from |feed_data| and fills |contacts_|.
408 // Returns true on success.
409 bool ProcessFeedData(const base::Value& feed_data) {
410 const DictionaryValue* toplevel_dict = NULL;
411 if (!feed_data.GetAsDictionary(&toplevel_dict)) {
412 LOG(WARNING) << "Top-level object is not a dictionary";
413 return false;
414 }
415
416 DictionaryValue* feed_dict = NULL;
417 if (!toplevel_dict->GetDictionary(kFeedField, &feed_dict)) {
418 LOG(WARNING) << "Feed dictionary missing";
419 return false;
420 }
421
422 // Check the category field to confirm that this is actually a contact feed.
423 ListValue* category_list = NULL;
424 if (!feed_dict->GetList(kCategoryField, &category_list)) {
425 LOG(WARNING) << "Category list missing";
426 return false;
427 }
428 DictionaryValue* category_dict = NULL;
429 if (!category_list->GetSize() == 1 ||
430 !category_list->GetDictionary(0, &category_dict)) {
431 LOG(WARNING) << "Unable to get dictionary from category list of size "
432 << category_list->GetSize();
433 return false;
434 }
435 std::string category_scheme, category_term;
436 if (!category_dict->GetString(kCategorySchemeField, &category_scheme) ||
437 !category_dict->GetString(kCategoryTermField, &category_term) ||
438 category_scheme != kCategorySchemeValue ||
439 category_term != kCategoryTermValue) {
440 LOG(WARNING) << "Unexpected category (scheme was \"" << category_scheme
441 << "\", term was \"" << category_term << "\")";
442 return false;
443 }
444
445 // A missing entry list means no entries (maybe we're doing an incremental
446 // update and nothing has changed).
447 ListValue* entry_list = NULL;
448 if (!feed_dict->GetList(kEntryField, &entry_list))
449 return true;
450
451 contacts_needing_photo_downloads_.reserve(entry_list->GetSize());
452
453 for (ListValue::const_iterator entry_it = entry_list->begin();
454 entry_it != entry_list->end(); ++entry_it) {
455 const size_t index = (entry_it - entry_list->begin());
456 const DictionaryValue* contact_dict = NULL;
457 if (!(*entry_it)->GetAsDictionary(&contact_dict)) {
458 LOG(WARNING) << "Entry " << index << " isn't a dictionary";
459 return false;
460 }
461
462 scoped_ptr<contacts::Contact> contact(new contacts::Contact);
463 if (!FillContactFromDictionary(*contact_dict, contact.get())) {
464 LOG(WARNING) << "Unable to fill entry " << index;
465 return false;
466 }
467
468 VLOG(1) << "Got contact " << index << ":"
469 << " id=" << contact->provider_id
470 << " full_name=\"" << contact->full_name << "\""
471 << " update_time=" << contact->update_time.ToDoubleT();
472
473 std::string photo_url;
474 if (GetPhotoUrl(*contact_dict, &photo_url)) {
475 if (!service_->feed_url_for_testing_.is_empty()) {
476 photo_url =
477 service_->rewrite_photo_url_callback_for_testing_.Run(photo_url);
478 }
479 contact_photo_urls_[contact.get()] = photo_url;
480 contacts_needing_photo_downloads_.push_back(contact.get());
481 }
482
483 contacts_->push_back(contact.release());
484 }
485
486 return true;
487 }
488
489 // If we're done downloading and decoding photos, invokes a callback and
490 // deletes |this|. Otherwise, starts one or more downloads of URLs from
491 // |contacts_needing_photo_downloads_|.
492 void CheckCompletion() {
493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
494 if (contacts_needing_photo_downloads_.empty() &&
495 num_in_progress_photo_downloads_ == 0 &&
496 in_progress_photo_decodes_.empty()) {
497 VLOG(1) << "Done downloading and decoding photos; invoking callback";
498 if (photo_download_failed_)
499 failure_callback_.Run();
500 else
501 success_callback_.Run(contacts_.Pass());
502 service_->OnRequestComplete(this);
503 return;
504 }
505
506 while (!contacts_needing_photo_downloads_.empty() &&
507 (num_in_progress_photo_downloads_ <
508 max_simultaneous_photo_downloads_)) {
509 contacts::Contact* contact = contacts_needing_photo_downloads_.back();
510 contacts_needing_photo_downloads_.pop_back();
511 DCHECK(contact_photo_urls_.count(contact));
512 std::string url = contact_photo_urls_[contact];
513
514 VLOG(1) << "Starting download of photo " << url << " for "
515 << contact->provider_id;
516 runner_->StartOperationWithRetry(
517 new GetContactPhotoOperation(
518 runner_->operation_registry(),
519 profile_,
520 GURL(url),
521 base::Bind(&DownloadContactsRequest::HandlePhotoData,
522 base::Unretained(this),
523 base::Unretained(contact))));
524 num_in_progress_photo_downloads_++;
525 }
526 }
527
528 // Callback for GetContactPhotoOperation calls. Decodes the image data,
529 // updates the associated Contact, and checks for completion.
530 void HandlePhotoData(contacts::Contact* contact,
531 GDataErrorCode error,
532 scoped_ptr<std::string> download_data) {
533 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
534 VLOG(1) << "Got photo data for " << contact->provider_id
535 << " (error=" << error << " size=" << download_data->size() << ")";
536 num_in_progress_photo_downloads_--;
537
538 if (error != HTTP_SUCCESS) {
539 LOG(WARNING) << "Got error " << error << " while downloading photo "
540 << "for " << contact->provider_id;
541 // TODO(derat): Retry several times for temporary failures?
542 photo_download_failed_ = true;
543 // Make sure we don't start any more downloads.
544 contacts_needing_photo_downloads_.clear();
545 CheckCompletion();
546 return;
547 }
548
549 scoped_refptr<ImageDecoder> image_decoder =
550 new ImageDecoder(this, *(download_data.get()));
551 in_progress_photo_decodes_.insert(
552 std::make_pair(image_decoder.get(), contact));
553 image_decoder->Start();
554 }
555
556 private:
557 typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls;
558 typedef std::map<ImageDecoder*, contacts::Contact*> ImageDecoderContactMap;
559
560 GDataContactsService* service_; // not owned
561 Profile* profile_; // not owned
562 GDataOperationRunner* runner_; // not owned
563
564 SuccessCallback success_callback_;
565 FailureCallback failure_callback_;
566
567 base::Time min_update_time_;
568
569 scoped_ptr<ScopedVector<contacts::Contact> > contacts_;
570
571 // Map from a contact to the URL at which its photo is located.
572 // Contacts without photos do not appear in this map.
573 ContactPhotoUrls contact_photo_urls_;
574
575 // Contacts that have photos that we still need to start downloading.
576 // When we start a download, the contact is removed from this list.
577 std::vector<contacts::Contact*> contacts_needing_photo_downloads_;
578
579 // Maximum number of photos we'll try to download at once.
580 int max_simultaneous_photo_downloads_;
581
582 // Number of in-progress photo downloads.
583 int num_in_progress_photo_downloads_;
584
585 // Did we encounter a fatal error while downloading a photo?
586 bool photo_download_failed_;
587
588 // Maps from an ImageDecoder to the contact whose photo it's decoding.
589 ImageDecoderContactMap in_progress_photo_decodes_;
590
591 DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest);
592 };
593
594 GDataContactsService::GDataContactsService(Profile* profile)
595 : profile_(profile),
596 runner_(new GDataOperationRunner(profile)),
597 max_simultaneous_photo_downloads_(kMaxSimultaneousPhotoDownloads) {
598 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
599 DCHECK(profile_);
600 }
601
602 GDataContactsService::~GDataContactsService() {
603 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
604 STLDeleteContainerPointers(requests_.begin(), requests_.end());
605 requests_.clear();
606 runner_->CancelAll();
607 }
608
609 GDataAuthService* GDataContactsService::auth_service_for_testing() {
610 return runner_->auth_service();
611 }
612
613 void GDataContactsService::Initialize() {
614 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
615 runner_->Initialize();
616 }
617
618 void GDataContactsService::DownloadContacts(SuccessCallback success_callback,
619 FailureCallback failure_callback,
620 const base::Time& min_update_time) {
621 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
622 DownloadContactsRequest* request =
623 new DownloadContactsRequest(this,
624 profile_,
625 runner_.get(),
626 success_callback,
627 failure_callback,
628 min_update_time,
629 max_simultaneous_photo_downloads_);
630 VLOG(1) << "Starting contacts download with request " << request;
631 requests_.insert(request);
632 request->Run();
633 }
634
635 void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) {
636 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
637 DCHECK(request);
638 VLOG(1) << "Download request " << request << " complete";
639 requests_.erase(request);
640 delete request;
641 }
642
643 } // namespace contacts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698