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

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

Issue 10823182: contacts: Rate-limit GData photo requests and handle 404s. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: merge Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698