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

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

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

Powered by Google App Engine
This is Rietveld 408576698