OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chromeos/gdata/gdata_contacts_service.h" | 5 #include "chrome/browser/chromeos/gdata/gdata_contacts_service.h" |
6 | 6 |
7 #include <cstring> | 7 #include <cstring> |
8 #include <string> | 8 #include <string> |
9 #include <map> | 9 #include <map> |
10 #include <utility> | 10 #include <utility> |
11 | 11 |
12 #include "base/json/json_writer.h" | 12 #include "base/json/json_writer.h" |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
14 #include "base/memory/weak_ptr.h" | 14 #include "base/memory/weak_ptr.h" |
15 #include "base/stl_util.h" | 15 #include "base/stl_util.h" |
| 16 #include "base/time.h" |
| 17 #include "base/timer.h" |
16 #include "base/values.h" | 18 #include "base/values.h" |
17 #include "chrome/browser/chromeos/contacts/contact.pb.h" | 19 #include "chrome/browser/chromeos/contacts/contact.pb.h" |
18 #include "chrome/browser/chromeos/gdata/gdata_operation_registry.h" | 20 #include "chrome/browser/chromeos/gdata/gdata_operation_registry.h" |
19 #include "chrome/browser/chromeos/gdata/gdata_operation_runner.h" | 21 #include "chrome/browser/chromeos/gdata/gdata_operation_runner.h" |
20 #include "chrome/browser/chromeos/gdata/gdata_operations.h" | 22 #include "chrome/browser/chromeos/gdata/gdata_operations.h" |
21 #include "chrome/browser/chromeos/gdata/gdata_params.h" | 23 #include "chrome/browser/chromeos/gdata/gdata_params.h" |
22 #include "chrome/browser/chromeos/gdata/gdata_util.h" | 24 #include "chrome/browser/chromeos/gdata/gdata_util.h" |
23 #include "content/public/browser/browser_thread.h" | 25 #include "content/public/browser/browser_thread.h" |
24 | 26 |
25 using content::BrowserThread; | 27 using content::BrowserThread; |
26 | 28 |
27 namespace gdata { | 29 namespace gdata { |
28 | 30 |
29 namespace { | 31 namespace { |
30 | 32 |
31 // Maximum number of profile photos that we'll download at once. | 33 // Maximum number of profile photos that we'll download per second. |
32 const int kMaxSimultaneousPhotoDownloads = 10; | 34 // At values above 10, Google starts returning 503 errors. |
| 35 const int kMaxPhotoDownloadsPerSecond = 10; |
33 | 36 |
34 // Field in the top-level object containing the contacts feed. | 37 // Field in the top-level object containing the contacts feed. |
35 const char kFeedField[] = "feed"; | 38 const char kFeedField[] = "feed"; |
36 | 39 |
37 // Field in the contacts feed containing a list of category information, along | 40 // Field in the contacts feed containing a list of category information, along |
38 // with fields within the dictionaries contained in the list and expected | 41 // with fields within the dictionaries contained in the list and expected |
39 // values. | 42 // values. |
40 const char kCategoryField[] = "category"; | 43 const char kCategoryField[] = "category"; |
41 const char kCategorySchemeField[] = "scheme"; | 44 const char kCategorySchemeField[] = "scheme"; |
42 const char kCategorySchemeValue[] = "http://schemas.google.com/g/2005#kind"; | 45 const char kCategorySchemeValue[] = "http://schemas.google.com/g/2005#kind"; |
(...skipping 269 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
312 // feed. Next, GetContactPhotoOperations are created and used to start | 315 // feed. Next, GetContactPhotoOperations are created and used to start |
313 // downloading contacts' photos in parallel. When all photos have been | 316 // downloading contacts' photos in parallel. When all photos have been |
314 // downloaded, the contacts are passed to the passed-in callback. | 317 // downloaded, the contacts are passed to the passed-in callback. |
315 class GDataContactsService::DownloadContactsRequest | 318 class GDataContactsService::DownloadContactsRequest |
316 : public base::SupportsWeakPtr<DownloadContactsRequest> { | 319 : public base::SupportsWeakPtr<DownloadContactsRequest> { |
317 public: | 320 public: |
318 DownloadContactsRequest(GDataContactsService* service, | 321 DownloadContactsRequest(GDataContactsService* service, |
319 GDataOperationRunner* runner, | 322 GDataOperationRunner* runner, |
320 SuccessCallback success_callback, | 323 SuccessCallback success_callback, |
321 FailureCallback failure_callback, | 324 FailureCallback failure_callback, |
322 const base::Time& min_update_time, | 325 const base::Time& min_update_time) |
323 int max_simultaneous_photo_downloads) | |
324 : service_(service), | 326 : service_(service), |
325 runner_(runner), | 327 runner_(runner), |
326 success_callback_(success_callback), | 328 success_callback_(success_callback), |
327 failure_callback_(failure_callback), | 329 failure_callback_(failure_callback), |
328 min_update_time_(min_update_time), | 330 min_update_time_(min_update_time), |
329 contacts_(new ScopedVector<contacts::Contact>), | 331 contacts_(new ScopedVector<contacts::Contact>), |
330 max_simultaneous_photo_downloads_(max_simultaneous_photo_downloads), | |
331 num_in_progress_photo_downloads_(0), | 332 num_in_progress_photo_downloads_(0), |
332 photo_download_failed_(false) { | 333 photo_download_failed_(false) { |
333 DCHECK(service_); | 334 DCHECK(service_); |
334 DCHECK(runner_); | 335 DCHECK(runner_); |
335 } | 336 } |
336 | 337 |
337 ~DownloadContactsRequest() { | 338 ~DownloadContactsRequest() { |
338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 339 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
339 service_ = NULL; | 340 service_ = NULL; |
340 runner_ = NULL; | 341 runner_ = NULL; |
(...skipping 26 matching lines...) Expand all Loading... |
367 } | 368 } |
368 | 369 |
369 VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get())); | 370 VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get())); |
370 if (!ProcessFeedData(*feed_data.get())) { | 371 if (!ProcessFeedData(*feed_data.get())) { |
371 LOG(WARNING) << "Unable to process feed data"; | 372 LOG(WARNING) << "Unable to process feed data"; |
372 failure_callback_.Run(); | 373 failure_callback_.Run(); |
373 service_->OnRequestComplete(this); | 374 service_->OnRequestComplete(this); |
374 return; | 375 return; |
375 } | 376 } |
376 | 377 |
| 378 StartPhotoDownloads(); |
| 379 photo_download_timer_.Start( |
| 380 FROM_HERE, service_->photo_download_timer_interval_, |
| 381 this, &DownloadContactsRequest::StartPhotoDownloads); |
377 CheckCompletion(); | 382 CheckCompletion(); |
378 } | 383 } |
379 | 384 |
380 // Processes the raw contacts feed from |feed_data| and fills |contacts_|. | 385 // Processes the raw contacts feed from |feed_data| and fills |contacts_|. |
381 // Returns true on success. | 386 // Returns true on success. |
382 bool ProcessFeedData(const base::Value& feed_data) { | 387 bool ProcessFeedData(const base::Value& feed_data) { |
383 const DictionaryValue* toplevel_dict = NULL; | 388 const DictionaryValue* toplevel_dict = NULL; |
384 if (!feed_data.GetAsDictionary(&toplevel_dict)) { | 389 if (!feed_data.GetAsDictionary(&toplevel_dict)) { |
385 LOG(WARNING) << "Top-level object is not a dictionary"; | 390 LOG(WARNING) << "Top-level object is not a dictionary"; |
386 return false; | 391 return false; |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
460 } | 465 } |
461 | 466 |
462 // If we're done downloading photos, invokes a callback and deletes |this|. | 467 // If we're done downloading photos, invokes a callback and deletes |this|. |
463 // Otherwise, starts one or more downloads of URLs from | 468 // Otherwise, starts one or more downloads of URLs from |
464 // |contacts_needing_photo_downloads_|. | 469 // |contacts_needing_photo_downloads_|. |
465 void CheckCompletion() { | 470 void CheckCompletion() { |
466 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 471 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
467 if (contacts_needing_photo_downloads_.empty() && | 472 if (contacts_needing_photo_downloads_.empty() && |
468 num_in_progress_photo_downloads_ == 0) { | 473 num_in_progress_photo_downloads_ == 0) { |
469 VLOG(1) << "Done downloading photos; invoking callback"; | 474 VLOG(1) << "Done downloading photos; invoking callback"; |
| 475 photo_download_timer_.Stop(); |
470 if (photo_download_failed_) | 476 if (photo_download_failed_) |
471 failure_callback_.Run(); | 477 failure_callback_.Run(); |
472 else | 478 else |
473 success_callback_.Run(contacts_.Pass()); | 479 success_callback_.Run(contacts_.Pass()); |
474 service_->OnRequestComplete(this); | 480 service_->OnRequestComplete(this); |
475 return; | 481 return; |
476 } | 482 } |
| 483 } |
477 | 484 |
| 485 // Starts photo downloads for contacts in |contacts_needing_photo_downloads_|. |
| 486 // Should be invoked only once per second. |
| 487 void StartPhotoDownloads() { |
478 while (!contacts_needing_photo_downloads_.empty() && | 488 while (!contacts_needing_photo_downloads_.empty() && |
479 (num_in_progress_photo_downloads_ < | 489 (num_in_progress_photo_downloads_ < |
480 max_simultaneous_photo_downloads_)) { | 490 service_->max_photo_downloads_per_second_)) { |
481 contacts::Contact* contact = contacts_needing_photo_downloads_.back(); | 491 contacts::Contact* contact = contacts_needing_photo_downloads_.back(); |
482 contacts_needing_photo_downloads_.pop_back(); | 492 contacts_needing_photo_downloads_.pop_back(); |
483 DCHECK(contact_photo_urls_.count(contact)); | 493 DCHECK(contact_photo_urls_.count(contact)); |
484 std::string url = contact_photo_urls_[contact]; | 494 std::string url = contact_photo_urls_[contact]; |
485 | 495 |
486 VLOG(1) << "Starting download of photo " << url << " for " | 496 VLOG(1) << "Starting download of photo " << url << " for " |
487 << contact->provider_id(); | 497 << contact->provider_id(); |
488 runner_->StartOperationWithRetry( | 498 runner_->StartOperationWithRetry( |
489 new GetContactPhotoOperation( | 499 new GetContactPhotoOperation( |
490 runner_->operation_registry(), | 500 runner_->operation_registry(), |
491 GURL(url), | 501 GURL(url), |
492 base::Bind(&DownloadContactsRequest::HandlePhotoData, | 502 base::Bind(&DownloadContactsRequest::HandlePhotoData, |
493 AsWeakPtr(), contact))); | 503 AsWeakPtr(), contact))); |
494 num_in_progress_photo_downloads_++; | 504 num_in_progress_photo_downloads_++; |
495 } | 505 } |
496 } | 506 } |
497 | 507 |
498 // Callback for GetContactPhotoOperation calls. Updates the associated | 508 // Callback for GetContactPhotoOperation calls. Updates the associated |
499 // Contact and checks for completion. | 509 // Contact and checks for completion. |
500 void HandlePhotoData(contacts::Contact* contact, | 510 void HandlePhotoData(contacts::Contact* contact, |
501 GDataErrorCode error, | 511 GDataErrorCode error, |
502 scoped_ptr<std::string> download_data) { | 512 scoped_ptr<std::string> download_data) { |
503 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 513 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
504 VLOG(1) << "Got photo data for " << contact->provider_id() | 514 VLOG(1) << "Got photo data for " << contact->provider_id() |
505 << " (error=" << error << " size=" << download_data->size() << ")"; | 515 << " (error=" << error << " size=" << download_data->size() << ")"; |
506 num_in_progress_photo_downloads_--; | 516 num_in_progress_photo_downloads_--; |
507 | 517 |
| 518 if (error == HTTP_INTERNAL_SERVER_ERROR || |
| 519 error == HTTP_SERVICE_UNAVAILABLE) { |
| 520 LOG(WARNING) << "Got error " << error << " while downloading photo " |
| 521 << "for " << contact->provider_id() << "; retrying"; |
| 522 contacts_needing_photo_downloads_.push_back(contact); |
| 523 return; |
| 524 } |
| 525 |
| 526 if (error == HTTP_NOT_FOUND) { |
| 527 LOG(WARNING) << "Got error " << error << " while downloading photo " |
| 528 << "for " << contact->provider_id() << "; skipping"; |
| 529 CheckCompletion(); |
| 530 return; |
| 531 } |
| 532 |
508 if (error != HTTP_SUCCESS) { | 533 if (error != HTTP_SUCCESS) { |
509 LOG(WARNING) << "Got error " << error << " while downloading photo " | 534 LOG(WARNING) << "Got error " << error << " while downloading photo " |
510 << "for " << contact->provider_id(); | 535 << "for " << contact->provider_id() << "; giving up"; |
511 // TODO(derat): Retry several times for temporary failures? | |
512 photo_download_failed_ = true; | 536 photo_download_failed_ = true; |
513 // Make sure we don't start any more downloads. | 537 // Make sure we don't start any more downloads. |
514 contacts_needing_photo_downloads_.clear(); | 538 contacts_needing_photo_downloads_.clear(); |
515 CheckCompletion(); | 539 CheckCompletion(); |
516 return; | 540 return; |
517 } | 541 } |
518 | 542 |
519 contact->set_raw_untrusted_photo(*download_data); | 543 contact->set_raw_untrusted_photo(*download_data); |
520 CheckCompletion(); | 544 CheckCompletion(); |
521 } | 545 } |
522 | 546 |
523 private: | 547 private: |
524 typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls; | 548 typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls; |
525 | 549 |
526 GDataContactsService* service_; // not owned | 550 GDataContactsService* service_; // not owned |
527 GDataOperationRunner* runner_; // not owned | 551 GDataOperationRunner* runner_; // not owned |
528 | 552 |
529 SuccessCallback success_callback_; | 553 SuccessCallback success_callback_; |
530 FailureCallback failure_callback_; | 554 FailureCallback failure_callback_; |
531 | 555 |
532 base::Time min_update_time_; | 556 base::Time min_update_time_; |
533 | 557 |
534 scoped_ptr<ScopedVector<contacts::Contact> > contacts_; | 558 scoped_ptr<ScopedVector<contacts::Contact> > contacts_; |
535 | 559 |
536 // Map from a contact to the URL at which its photo is located. | 560 // Map from a contact to the URL at which its photo is located. |
537 // Contacts without photos do not appear in this map. | 561 // Contacts without photos do not appear in this map. |
538 ContactPhotoUrls contact_photo_urls_; | 562 ContactPhotoUrls contact_photo_urls_; |
539 | 563 |
| 564 // Invokes StartPhotoDownloads() once per second. |
| 565 base::RepeatingTimer<DownloadContactsRequest> photo_download_timer_; |
| 566 |
540 // Contacts that have photos that we still need to start downloading. | 567 // Contacts that have photos that we still need to start downloading. |
541 // When we start a download, the contact is removed from this list. | 568 // When we start a download, the contact is removed from this list. |
542 std::vector<contacts::Contact*> contacts_needing_photo_downloads_; | 569 std::vector<contacts::Contact*> contacts_needing_photo_downloads_; |
543 | 570 |
544 // Maximum number of photos we'll try to download at once. | |
545 int max_simultaneous_photo_downloads_; | |
546 | |
547 // Number of in-progress photo downloads. | 571 // Number of in-progress photo downloads. |
548 int num_in_progress_photo_downloads_; | 572 int num_in_progress_photo_downloads_; |
549 | 573 |
550 // Did we encounter a fatal error while downloading a photo? | 574 // Did we encounter a fatal error while downloading a photo? |
551 bool photo_download_failed_; | 575 bool photo_download_failed_; |
552 | 576 |
553 DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest); | 577 DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest); |
554 }; | 578 }; |
555 | 579 |
556 GDataContactsService::GDataContactsService(Profile* profile) | 580 GDataContactsService::GDataContactsService(Profile* profile) |
557 : runner_(new GDataOperationRunner(profile)), | 581 : runner_(new GDataOperationRunner(profile)), |
558 max_simultaneous_photo_downloads_(kMaxSimultaneousPhotoDownloads) { | 582 max_photo_downloads_per_second_(kMaxPhotoDownloadsPerSecond), |
| 583 photo_download_timer_interval_(base::TimeDelta::FromSeconds(1)) { |
559 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 584 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
560 } | 585 } |
561 | 586 |
562 GDataContactsService::~GDataContactsService() { | 587 GDataContactsService::~GDataContactsService() { |
563 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 588 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
564 runner_->CancelAll(); | 589 runner_->CancelAll(); |
565 STLDeleteContainerPointers(requests_.begin(), requests_.end()); | 590 STLDeleteContainerPointers(requests_.begin(), requests_.end()); |
566 requests_.clear(); | 591 requests_.clear(); |
567 } | 592 } |
568 | 593 |
569 GDataAuthService* GDataContactsService::auth_service_for_testing() { | 594 GDataAuthService* GDataContactsService::auth_service_for_testing() { |
570 return runner_->auth_service(); | 595 return runner_->auth_service(); |
571 } | 596 } |
572 | 597 |
573 void GDataContactsService::Initialize() { | 598 void GDataContactsService::Initialize() { |
574 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 599 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
575 runner_->Initialize(); | 600 runner_->Initialize(); |
576 } | 601 } |
577 | 602 |
578 void GDataContactsService::DownloadContacts(SuccessCallback success_callback, | 603 void GDataContactsService::DownloadContacts(SuccessCallback success_callback, |
579 FailureCallback failure_callback, | 604 FailureCallback failure_callback, |
580 const base::Time& min_update_time) { | 605 const base::Time& min_update_time) { |
581 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 606 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
582 DownloadContactsRequest* request = | 607 DownloadContactsRequest* request = |
583 new DownloadContactsRequest(this, | 608 new DownloadContactsRequest(this, |
584 runner_.get(), | 609 runner_.get(), |
585 success_callback, | 610 success_callback, |
586 failure_callback, | 611 failure_callback, |
587 min_update_time, | 612 min_update_time); |
588 max_simultaneous_photo_downloads_); | |
589 VLOG(1) << "Starting contacts download with request " << request; | 613 VLOG(1) << "Starting contacts download with request " << request; |
590 requests_.insert(request); | 614 requests_.insert(request); |
591 request->Run(); | 615 request->Run(); |
592 } | 616 } |
593 | 617 |
594 void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) { | 618 void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) { |
595 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 619 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
596 DCHECK(request); | 620 DCHECK(request); |
597 VLOG(1) << "Download request " << request << " complete"; | 621 VLOG(1) << "Download request " << request << " complete"; |
598 requests_.erase(request); | 622 requests_.erase(request); |
599 delete request; | 623 delete request; |
600 } | 624 } |
601 | 625 |
602 } // namespace contacts | 626 } // namespace contacts |
OLD | NEW |