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

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: apply review feedback 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
satorux1 2012/07/24 21:34:29 nit: an AddressType
Daniel Erat 2012/07/27 16:54:19 Done.
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 // Gets the photo URL from a contact's dictionary (within the "entry" list).
173 // Returns an empty string if no photo was found.
174 std::string GetPhotoUrl(const DictionaryValue& dict) {
175 ListValue* link_list = NULL;
176 if (!dict.GetList(kLinkField, &link_list))
177 return std::string();
178
179 for (size_t i = 0; i < link_list->GetSize(); ++i) {
180 DictionaryValue* link_dict = NULL;
181 if (!link_list->GetDictionary(i, &link_dict))
182 continue;
183
184 std::string rel;
185 if (!link_dict->GetString(kLinkRelField, &rel))
186 continue;
187 if (rel != kLinkRelPhotoValue)
188 continue;
189
190 // From https://goo.gl/7T6Od: "If a contact does not have a photo, then the
191 // photo link element has no gd:etag attribute."
192 std::string etag;
193 if (!link_dict->GetString(kLinkETagField, &etag))
194 continue;
195
196 std::string url;
197 if (link_dict->GetString(kLinkHrefField, &url))
198 return url;
199 }
200 return std::string();
201 }
202
203 // Fills a Contact's fields using an entry from a GData feed.
204 bool FillContactFromDictionary(const base::DictionaryValue& dict,
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 // This class handles a single request to download all of a user's contacts.
305 //
306 // First, the contacts feed is downloaded via GetContactsOperation and parsed.
307 // Individual contacts::Contact objects are created using the data from the
308 // feed.
309 //
310 // Next, GetContactPhotoOperations are created and used to start downloading
311 // contacts' photos in parallel. As the operations finish, sandboxed
312 // ImageDecoders are created to convert the image data to SkBitmaps and
313 // additional GetContactPhotoOperations are created.
314 //
315 // When all photos have been downloaded and decoded, the contacts are passed to
316 // the passed-in callback.
317 class GDataContactsService::DownloadContactsRequest
318 : public ImageDecoder::Delegate {
319 public:
320 DownloadContactsRequest(GDataContactsService* service,
321 Profile* profile,
322 GDataOperationRunner* runner,
323 SuccessCallback success_callback,
324 FailureCallback failure_callback,
325 const base::Time& min_update_time,
326 int max_simultaneous_photo_downloads)
327 : service_(service),
328 profile_(profile),
329 runner_(runner),
330 success_callback_(success_callback),
331 failure_callback_(failure_callback),
332 min_update_time_(min_update_time),
333 contacts_(new ScopedVector<contacts::Contact>),
334 max_simultaneous_photo_downloads_(max_simultaneous_photo_downloads),
335 num_in_progress_photo_downloads_(0),
336 photo_download_failed_(false) {
337 DCHECK(service_);
338 DCHECK(profile_);
339 DCHECK(runner_);
340 }
341
342 ~DownloadContactsRequest() {
343 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
344 // Abandon any outstanding image decode requests.
345 for (ImageDecoderContactMap::iterator it =
346 in_progress_photo_decodes_.begin();
347 it != in_progress_photo_decodes_.end(); ++it) {
348 it->first->set_delegate(NULL);
349 }
350 service_ = NULL;
351 profile_ = NULL;
352 runner_ = NULL;
353 }
354
355 // Issues the initial request to download the contact feed.
356 void Run() {
357 GetContactsOperation* operation =
358 new GetContactsOperation(
359 runner_->operation_registry(),
360 profile_,
361 min_update_time_,
362 base::Bind(&DownloadContactsRequest::HandleFeedData,
363 base::Unretained(this)));
364 if (!service_->feed_url_for_testing_.is_empty())
365 operation->set_feed_url_for_testing(service_->feed_url_for_testing_);
366
367 runner_->StartOperationWithRetry(operation);
368 }
369
370 // ImageDecoder::Delegate overrides:
371 virtual void OnImageDecoded(const ImageDecoder* decoder,
372 const SkBitmap& decoded_image) OVERRIDE {
373 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
374 ImageDecoderContactMap::iterator it =
375 in_progress_photo_decodes_.find(const_cast<ImageDecoder*>(decoder));
376 CHECK(it != in_progress_photo_decodes_.end());
377 contacts::Contact* contact = it->second;
378 VLOG(1) << "Downloaded and decoded " << decoded_image.width() << "x"
379 << decoded_image.height() << " photo for " << contact->provider_id;
380 contact->photo = decoded_image;
satorux1 2012/07/24 21:34:29 just curious but how big is the decoded image in b
Daniel Erat 2012/07/27 16:54:19 I've switched this to re-encode the image as a PNG
381 in_progress_photo_decodes_.erase(it);
382 CheckCompletion();
383 }
384
385 virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE {
386 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
387 ImageDecoderContactMap::iterator it =
388 in_progress_photo_decodes_.find(const_cast<ImageDecoder*>(decoder));
389 CHECK(it != in_progress_photo_decodes_.end());
390 contacts::Contact* contact = it->second;
391 LOG(WARNING) << "Failed to decode image for contact "
392 << contact->provider_id;
393 // Leave the photo blank but don't abort the entire download request.
394 in_progress_photo_decodes_.erase(it);
395 CheckCompletion();
396 }
397
398 private:
399 // Callback for GetContactsOperation calls.
400 void HandleFeedData(GDataErrorCode error,
401 scoped_ptr<base::Value> feed_data) {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
403 if (error != HTTP_SUCCESS) {
404 LOG(WARNING) << "Got error " << error << " while downloading contacts";
405 failure_callback_.Run();
406 service_->OnRequestComplete(this);
407 return;
408 }
409
410 VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get()));
411 if (!ProcessFeedData(*feed_data.get())) {
412 LOG(WARNING) << "Unable to process feed data";
413 failure_callback_.Run();
414 service_->OnRequestComplete(this);
415 return;
416 }
417
418 CheckCompletion();
419 }
420
421 // Processes the raw contacts feed from |feed_data| and fills |contacts_|.
422 // Returns true on success.
423 bool ProcessFeedData(const base::Value& feed_data) {
424 const DictionaryValue* toplevel_dict = NULL;
425 if (!feed_data.GetAsDictionary(&toplevel_dict)) {
426 LOG(WARNING) << "Top-level object is not a dictionary";
427 return false;
428 }
429
430 DictionaryValue* feed_dict = NULL;
431 if (!toplevel_dict->GetDictionary(kFeedField, &feed_dict)) {
432 LOG(WARNING) << "Feed dictionary missing";
433 return false;
434 }
435
436 // Check the category field to confirm that this is actually a contact feed.
437 ListValue* category_list = NULL;
438 if (!feed_dict->GetList(kCategoryField, &category_list)) {
439 LOG(WARNING) << "Category list missing";
440 return false;
441 }
442 DictionaryValue* category_dict = NULL;
443 if (!category_list->GetSize() == 1 ||
444 !category_list->GetDictionary(0, &category_dict)) {
445 LOG(WARNING) << "Unable to get dictionary from category list of size "
446 << category_list->GetSize();
447 return false;
448 }
449 std::string category_scheme, category_term;
450 if (!category_dict->GetString(kCategorySchemeField, &category_scheme) ||
451 !category_dict->GetString(kCategoryTermField, &category_term) ||
452 category_scheme != kCategorySchemeValue ||
453 category_term != kCategoryTermValue) {
454 LOG(WARNING) << "Unexpected category (scheme was \"" << category_scheme
455 << "\", term was \"" << category_term << "\")";
456 return false;
457 }
458
459 // A missing entry list means no entries (maybe we're doing an incremental
460 // update and nothing has changed).
461 ListValue* entry_list = NULL;
462 if (!feed_dict->GetList(kEntryField, &entry_list))
463 return true;
464
465 contacts_needing_photo_downloads_.reserve(entry_list->GetSize());
466
467 for (ListValue::const_iterator entry_it = entry_list->begin();
468 entry_it != entry_list->end(); ++entry_it) {
469 const size_t index = (entry_it - entry_list->begin());
470 const DictionaryValue* contact_dict = NULL;
471 if (!(*entry_it)->GetAsDictionary(&contact_dict)) {
472 LOG(WARNING) << "Entry " << index << " isn't a dictionary";
473 return false;
474 }
475
476 scoped_ptr<contacts::Contact> contact(new contacts::Contact);
477 if (!FillContactFromDictionary(*contact_dict, contact.get())) {
478 LOG(WARNING) << "Unable to fill entry " << index;
479 return false;
480 }
481
482 VLOG(1) << "Got contact " << index << ":"
483 << " id=" << contact->provider_id
484 << " full_name=\"" << contact->full_name << "\""
485 << " update_time=" << contact->update_time.ToDoubleT();
486
487 std::string photo_url = GetPhotoUrl(*contact_dict);
488 if (!photo_url.empty()) {
489 if (!service_->feed_url_for_testing_.is_empty()) {
satorux1 2012/07/24 21:34:29 did you mean rewrite_photo_url_callback_for_testin
Daniel Erat 2012/07/27 16:54:19 No. Callbacks can't be empty, so I was using this
satorux1 2012/07/28 00:38:23 I meant rewrite_photo_url_callback_for_testing_.is
Daniel Erat 2012/07/29 19:54:25 Ah, thanks. I'd looked for is_null() in base/call
490 photo_url =
491 service_->rewrite_photo_url_callback_for_testing_.Run(photo_url);
satorux1 2012/07/24 21:34:29 This is tricky. Might want to comment why we need
Daniel Erat 2012/07/27 16:54:19 Added a comment to the header.
492 }
493 contact_photo_urls_[contact.get()] = photo_url;
494 contacts_needing_photo_downloads_.push_back(contact.get());
495 }
496
497 contacts_->push_back(contact.release());
498 }
499
500 return true;
501 }
502
503 // If we're done downloading and decoding photos, invokes a callback and
504 // deletes |this|. Otherwise, starts one or more downloads of URLs from
505 // |contacts_needing_photo_downloads_|.
506 void CheckCompletion() {
507 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
508 if (contacts_needing_photo_downloads_.empty() &&
509 num_in_progress_photo_downloads_ == 0 &&
510 in_progress_photo_decodes_.empty()) {
511 VLOG(1) << "Done downloading and decoding photos; invoking callback";
512 if (photo_download_failed_)
513 failure_callback_.Run();
514 else
515 success_callback_.Run(contacts_.Pass());
516 service_->OnRequestComplete(this);
517 return;
518 }
519
520 while (!contacts_needing_photo_downloads_.empty() &&
521 (num_in_progress_photo_downloads_ <
522 max_simultaneous_photo_downloads_)) {
523 contacts::Contact* contact = contacts_needing_photo_downloads_.back();
524 contacts_needing_photo_downloads_.pop_back();
525 DCHECK(contact_photo_urls_.count(contact));
526 std::string url = contact_photo_urls_[contact];
527
528 VLOG(1) << "Starting download of photo " << url << " for "
529 << contact->provider_id;
530 runner_->StartOperationWithRetry(
531 new GetContactPhotoOperation(
532 runner_->operation_registry(),
533 profile_,
534 GURL(url),
535 base::Bind(&DownloadContactsRequest::HandlePhotoData,
536 base::Unretained(this),
satorux1 2012/07/24 21:34:29 It it safe to use base::Unretained() here? I guess
Daniel Erat 2012/07/27 16:54:19 Is your concern that the GetContactPhotoOperation
537 base::Unretained(contact))));
satorux1 2012/07/24 21:34:29 hmm, using base::Unretained() for the 2nd paramete
Daniel Erat 2012/07/27 16:54:19 I don't think so; deleted.
538 num_in_progress_photo_downloads_++;
539 }
540 }
541
542 // Callback for GetContactPhotoOperation calls. Decodes the image data,
543 // updates the associated Contact, and checks for completion.
544 void HandlePhotoData(contacts::Contact* contact,
545 GDataErrorCode error,
546 scoped_ptr<std::string> download_data) {
547 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
548 VLOG(1) << "Got photo data for " << contact->provider_id
549 << " (error=" << error << " size=" << download_data->size() << ")";
550 num_in_progress_photo_downloads_--;
551
552 if (error != HTTP_SUCCESS) {
553 LOG(WARNING) << "Got error " << error << " while downloading photo "
554 << "for " << contact->provider_id;
555 // TODO(derat): Retry several times for temporary failures?
556 photo_download_failed_ = true;
557 // Make sure we don't start any more downloads.
558 contacts_needing_photo_downloads_.clear();
559 CheckCompletion();
560 return;
561 }
562
563 scoped_refptr<ImageDecoder> image_decoder =
564 new ImageDecoder(this, *(download_data.get()));
565 in_progress_photo_decodes_.insert(
566 std::make_pair(image_decoder.get(), contact));
567 image_decoder->Start();
568 }
569
570 private:
571 typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls;
572 typedef std::map<ImageDecoder*, contacts::Contact*> ImageDecoderContactMap;
573
574 GDataContactsService* service_; // not owned
575 Profile* profile_; // not owned
576 GDataOperationRunner* runner_; // not owned
577
578 SuccessCallback success_callback_;
579 FailureCallback failure_callback_;
580
581 base::Time min_update_time_;
582
583 scoped_ptr<ScopedVector<contacts::Contact> > contacts_;
584
585 // Map from a contact to the URL at which its photo is located.
586 // Contacts without photos do not appear in this map.
587 ContactPhotoUrls contact_photo_urls_;
588
589 // Contacts that have photos that we still need to start downloading.
590 // When we start a download, the contact is removed from this list.
591 std::vector<contacts::Contact*> contacts_needing_photo_downloads_;
592
593 // Maximum number of photos we'll try to download at once.
594 int max_simultaneous_photo_downloads_;
595
596 // Number of in-progress photo downloads.
597 int num_in_progress_photo_downloads_;
598
599 // Did we encounter a fatal error while downloading a photo?
600 bool photo_download_failed_;
601
602 // Maps from an ImageDecoder to the contact whose photo it's decoding.
603 ImageDecoderContactMap in_progress_photo_decodes_;
604
605 DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest);
606 };
607
608 GDataContactsService::GDataContactsService(Profile* profile)
609 : profile_(profile),
610 runner_(new GDataOperationRunner(profile)),
611 max_simultaneous_photo_downloads_(kMaxSimultaneousPhotoDownloads) {
612 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
613 DCHECK(profile_);
614 }
615
616 GDataContactsService::~GDataContactsService() {
617 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
618 STLDeleteContainerPointers(requests_.begin(), requests_.end());
619 requests_.clear();
620 runner_->CancelAll();
621 }
622
623 GDataAuthService* GDataContactsService::auth_service_for_testing() {
624 return runner_->auth_service();
625 }
626
627 void GDataContactsService::Initialize() {
628 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
629 runner_->Initialize();
630 }
631
632 void GDataContactsService::DownloadContacts(SuccessCallback success_callback,
633 FailureCallback failure_callback,
634 const base::Time& min_update_time) {
635 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
636 DownloadContactsRequest* request =
637 new DownloadContactsRequest(this,
638 profile_,
639 runner_.get(),
640 success_callback,
641 failure_callback,
642 min_update_time,
643 max_simultaneous_photo_downloads_);
644 VLOG(1) << "Starting contacts download with request " << request;
645 requests_.insert(request);
646 request->Run();
647 }
648
649 void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) {
650 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
651 DCHECK(request);
652 VLOG(1) << "Download request " << request << " complete";
653 requests_.erase(request);
654 delete request;
655 }
656
657 } // namespace contacts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698