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

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: 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 "chrome/browser/profiles/profile.h" 25 #include "chrome/browser/profiles/profile.h"
24 #include "content/public/browser/browser_thread.h" 26 #include "content/public/browser/browser_thread.h"
25 27
26 using content::BrowserThread; 28 using content::BrowserThread;
27 29
28 namespace gdata { 30 namespace gdata {
29 31
30 namespace { 32 namespace {
31 33
32 // Maximum number of profile photos that we'll download at once. 34 // Maximum number of profile photos that we'll download per second.
33 const int kMaxSimultaneousPhotoDownloads = 10; 35 // At values above 10, Google starts returning 503 errors.
36 const int kMaxPhotoDownloadsPerSecond = 10;
34 37
35 // Field in the top-level object containing the contacts feed. 38 // Field in the top-level object containing the contacts feed.
36 const char kFeedField[] = "feed"; 39 const char kFeedField[] = "feed";
37 40
38 // Field in the contacts feed containing a list of category information, along 41 // Field in the contacts feed containing a list of category information, along
39 // with fields within the dictionaries contained in the list and expected 42 // with fields within the dictionaries contained in the list and expected
40 // values. 43 // values.
41 const char kCategoryField[] = "category"; 44 const char kCategoryField[] = "category";
42 const char kCategorySchemeField[] = "scheme"; 45 const char kCategorySchemeField[] = "scheme";
43 const char kCategorySchemeValue[] = "http://schemas.google.com/g/2005#kind"; 46 const char kCategorySchemeValue[] = "http://schemas.google.com/g/2005#kind";
(...skipping 271 matching lines...) Expand 10 before | Expand all | Expand 10 after
315 // downloaded, the contacts are passed to the passed-in callback. 318 // downloaded, the contacts are passed to the passed-in callback.
316 class GDataContactsService::DownloadContactsRequest 319 class GDataContactsService::DownloadContactsRequest
317 : public base::SupportsWeakPtr<DownloadContactsRequest> { 320 : public base::SupportsWeakPtr<DownloadContactsRequest> {
318 public: 321 public:
319 DownloadContactsRequest(GDataContactsService* service, 322 DownloadContactsRequest(GDataContactsService* service,
320 Profile* profile, 323 Profile* profile,
321 GDataOperationRunner* runner, 324 GDataOperationRunner* runner,
322 SuccessCallback success_callback, 325 SuccessCallback success_callback,
323 FailureCallback failure_callback, 326 FailureCallback failure_callback,
324 const base::Time& min_update_time, 327 const base::Time& min_update_time,
325 int max_simultaneous_photo_downloads) 328 int max_photo_downloads_per_second)
326 : service_(service), 329 : service_(service),
327 profile_(profile), 330 profile_(profile),
328 runner_(runner), 331 runner_(runner),
329 success_callback_(success_callback), 332 success_callback_(success_callback),
330 failure_callback_(failure_callback), 333 failure_callback_(failure_callback),
331 min_update_time_(min_update_time), 334 min_update_time_(min_update_time),
332 contacts_(new ScopedVector<contacts::Contact>), 335 contacts_(new ScopedVector<contacts::Contact>),
333 max_simultaneous_photo_downloads_(max_simultaneous_photo_downloads), 336 max_photo_downloads_per_second_(max_photo_downloads_per_second),
334 num_in_progress_photo_downloads_(0), 337 num_in_progress_photo_downloads_(0),
335 photo_download_failed_(false) { 338 photo_download_failed_(false) {
336 DCHECK(service_); 339 DCHECK(service_);
337 DCHECK(profile_); 340 DCHECK(profile_);
338 DCHECK(runner_); 341 DCHECK(runner_);
339 } 342 }
340 343
341 ~DownloadContactsRequest() { 344 ~DownloadContactsRequest() {
342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
343 service_ = NULL; 346 service_ = NULL;
(...skipping 29 matching lines...) Expand all
373 } 376 }
374 377
375 VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get())); 378 VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get()));
376 if (!ProcessFeedData(*feed_data.get())) { 379 if (!ProcessFeedData(*feed_data.get())) {
377 LOG(WARNING) << "Unable to process feed data"; 380 LOG(WARNING) << "Unable to process feed data";
378 failure_callback_.Run(); 381 failure_callback_.Run();
379 service_->OnRequestComplete(this); 382 service_->OnRequestComplete(this);
380 return; 383 return;
381 } 384 }
382 385
386 StartPhotoDownloads();
387 photo_download_timer_.Start(
388 FROM_HERE, base::TimeDelta::FromSeconds(1),
389 this, &DownloadContactsRequest::StartPhotoDownloads);
383 CheckCompletion(); 390 CheckCompletion();
384 } 391 }
385 392
386 // Processes the raw contacts feed from |feed_data| and fills |contacts_|. 393 // Processes the raw contacts feed from |feed_data| and fills |contacts_|.
387 // Returns true on success. 394 // Returns true on success.
388 bool ProcessFeedData(const base::Value& feed_data) { 395 bool ProcessFeedData(const base::Value& feed_data) {
389 const DictionaryValue* toplevel_dict = NULL; 396 const DictionaryValue* toplevel_dict = NULL;
390 if (!feed_data.GetAsDictionary(&toplevel_dict)) { 397 if (!feed_data.GetAsDictionary(&toplevel_dict)) {
391 LOG(WARNING) << "Top-level object is not a dictionary"; 398 LOG(WARNING) << "Top-level object is not a dictionary";
392 return false; 399 return false;
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
466 } 473 }
467 474
468 // If we're done downloading photos, invokes a callback and deletes |this|. 475 // If we're done downloading photos, invokes a callback and deletes |this|.
469 // Otherwise, starts one or more downloads of URLs from 476 // Otherwise, starts one or more downloads of URLs from
470 // |contacts_needing_photo_downloads_|. 477 // |contacts_needing_photo_downloads_|.
471 void CheckCompletion() { 478 void CheckCompletion() {
472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 479 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
473 if (contacts_needing_photo_downloads_.empty() && 480 if (contacts_needing_photo_downloads_.empty() &&
474 num_in_progress_photo_downloads_ == 0) { 481 num_in_progress_photo_downloads_ == 0) {
475 VLOG(1) << "Done downloading photos; invoking callback"; 482 VLOG(1) << "Done downloading photos; invoking callback";
483 photo_download_timer_.Stop();
476 if (photo_download_failed_) 484 if (photo_download_failed_)
477 failure_callback_.Run(); 485 failure_callback_.Run();
478 else 486 else
479 success_callback_.Run(contacts_.Pass()); 487 success_callback_.Run(contacts_.Pass());
480 service_->OnRequestComplete(this); 488 service_->OnRequestComplete(this);
481 return; 489 return;
482 } 490 }
491 }
483 492
493 // Starts photo downloads for contacts in |contacts_needing_photo_downloads_|.
494 // Should be invoked only once per second.
495 void StartPhotoDownloads() {
484 while (!contacts_needing_photo_downloads_.empty() && 496 while (!contacts_needing_photo_downloads_.empty() &&
485 (num_in_progress_photo_downloads_ < 497 (num_in_progress_photo_downloads_ <
486 max_simultaneous_photo_downloads_)) { 498 max_photo_downloads_per_second_)) {
487 contacts::Contact* contact = contacts_needing_photo_downloads_.back(); 499 contacts::Contact* contact = contacts_needing_photo_downloads_.back();
488 contacts_needing_photo_downloads_.pop_back(); 500 contacts_needing_photo_downloads_.pop_back();
489 DCHECK(contact_photo_urls_.count(contact)); 501 DCHECK(contact_photo_urls_.count(contact));
490 std::string url = contact_photo_urls_[contact]; 502 std::string url = contact_photo_urls_[contact];
491 503
492 VLOG(1) << "Starting download of photo " << url << " for " 504 VLOG(1) << "Starting download of photo " << url << " for "
493 << contact->provider_id(); 505 << contact->provider_id();
494 runner_->StartOperationWithRetry( 506 runner_->StartOperationWithRetry(
495 new GetContactPhotoOperation( 507 new GetContactPhotoOperation(
496 runner_->operation_registry(), 508 runner_->operation_registry(),
497 profile_, 509 profile_,
498 GURL(url), 510 GURL(url),
499 base::Bind(&DownloadContactsRequest::HandlePhotoData, 511 base::Bind(&DownloadContactsRequest::HandlePhotoData,
500 AsWeakPtr(), contact))); 512 AsWeakPtr(), contact)));
501 num_in_progress_photo_downloads_++; 513 num_in_progress_photo_downloads_++;
502 } 514 }
503 } 515 }
504 516
505 // Callback for GetContactPhotoOperation calls. Updates the associated 517 // Callback for GetContactPhotoOperation calls. Updates the associated
506 // Contact and checks for completion. 518 // Contact and checks for completion.
507 void HandlePhotoData(contacts::Contact* contact, 519 void HandlePhotoData(contacts::Contact* contact,
508 GDataErrorCode error, 520 GDataErrorCode error,
509 scoped_ptr<std::string> download_data) { 521 scoped_ptr<std::string> download_data) {
510 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 522 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
511 VLOG(1) << "Got photo data for " << contact->provider_id() 523 VLOG(1) << "Got photo data for " << contact->provider_id()
512 << " (error=" << error << " size=" << download_data->size() << ")"; 524 << " (error=" << error << " size=" << download_data->size() << ")";
513 num_in_progress_photo_downloads_--; 525 num_in_progress_photo_downloads_--;
514 526
527 if (error == HTTP_INTERNAL_SERVER_ERROR ||
528 error == HTTP_SERVICE_UNAVAILABLE) {
529 LOG(WARNING) << "Got error " << error << " while downloading photo "
530 << "for " << contact->provider_id() << "; retrying";
531 contacts_needing_photo_downloads_.push_back(contact);
532 return;
533 }
534
535 if (error == HTTP_NOT_FOUND) {
536 LOG(WARNING) << "Got error " << error << " while downloading photo "
537 << "for " << contact->provider_id() << "; skipping";
538 CheckCompletion();
539 return;
540 }
541
515 if (error != HTTP_SUCCESS) { 542 if (error != HTTP_SUCCESS) {
516 LOG(WARNING) << "Got error " << error << " while downloading photo " 543 LOG(WARNING) << "Got error " << error << " while downloading photo "
517 << "for " << contact->provider_id(); 544 << "for " << contact->provider_id() << "; giving up";
satorux1 2012/08/06 20:57:08 don't have to do it in the patch, but you might wa
Daniel Erat 2012/08/06 23:12:21 Yep, I plan to add a lot of metrics in a later pat
518 // TODO(derat): Retry several times for temporary failures?
519 photo_download_failed_ = true; 545 photo_download_failed_ = true;
520 // Make sure we don't start any more downloads. 546 // Make sure we don't start any more downloads.
521 contacts_needing_photo_downloads_.clear(); 547 contacts_needing_photo_downloads_.clear();
522 CheckCompletion(); 548 CheckCompletion();
523 return; 549 return;
524 } 550 }
525 551
526 contact->set_raw_untrusted_photo(*download_data); 552 contact->set_raw_untrusted_photo(*download_data);
527 CheckCompletion(); 553 CheckCompletion();
528 } 554 }
529 555
530 private: 556 private:
531 typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls; 557 typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls;
532 558
533 GDataContactsService* service_; // not owned 559 GDataContactsService* service_; // not owned
534 Profile* profile_; // not owned 560 Profile* profile_; // not owned
535 GDataOperationRunner* runner_; // not owned 561 GDataOperationRunner* runner_; // not owned
536 562
537 SuccessCallback success_callback_; 563 SuccessCallback success_callback_;
538 FailureCallback failure_callback_; 564 FailureCallback failure_callback_;
539 565
540 base::Time min_update_time_; 566 base::Time min_update_time_;
541 567
542 scoped_ptr<ScopedVector<contacts::Contact> > contacts_; 568 scoped_ptr<ScopedVector<contacts::Contact> > contacts_;
543 569
544 // Map from a contact to the URL at which its photo is located. 570 // Map from a contact to the URL at which its photo is located.
545 // Contacts without photos do not appear in this map. 571 // Contacts without photos do not appear in this map.
546 ContactPhotoUrls contact_photo_urls_; 572 ContactPhotoUrls contact_photo_urls_;
547 573
574 // Invokes StartPhotoDownloads() once per second.
575 base::RepeatingTimer<DownloadContactsRequest> photo_download_timer_;
576
548 // Contacts that have photos that we still need to start downloading. 577 // Contacts that have photos that we still need to start downloading.
549 // When we start a download, the contact is removed from this list. 578 // When we start a download, the contact is removed from this list.
550 std::vector<contacts::Contact*> contacts_needing_photo_downloads_; 579 std::vector<contacts::Contact*> contacts_needing_photo_downloads_;
551 580
552 // Maximum number of photos we'll try to download at once. 581 // Maximum number of photos we'll try to download per second.
553 int max_simultaneous_photo_downloads_; 582 int max_photo_downloads_per_second_;
554 583
555 // Number of in-progress photo downloads. 584 // Number of in-progress photo downloads.
556 int num_in_progress_photo_downloads_; 585 int num_in_progress_photo_downloads_;
557 586
558 // Did we encounter a fatal error while downloading a photo? 587 // Did we encounter a fatal error while downloading a photo?
559 bool photo_download_failed_; 588 bool photo_download_failed_;
560 589
561 DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest); 590 DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest);
562 }; 591 };
563 592
564 GDataContactsService::GDataContactsService(Profile* profile) 593 GDataContactsService::GDataContactsService(Profile* profile)
565 : profile_(profile), 594 : profile_(profile),
566 runner_(new GDataOperationRunner(profile)), 595 runner_(new GDataOperationRunner(profile)),
567 max_simultaneous_photo_downloads_(kMaxSimultaneousPhotoDownloads) { 596 max_photo_downloads_per_second_(kMaxPhotoDownloadsPerSecond) {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 597 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
569 DCHECK(profile_); 598 DCHECK(profile_);
570 } 599 }
571 600
572 GDataContactsService::~GDataContactsService() { 601 GDataContactsService::~GDataContactsService() {
573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 602 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
574 runner_->CancelAll(); 603 runner_->CancelAll();
575 STLDeleteContainerPointers(requests_.begin(), requests_.end()); 604 STLDeleteContainerPointers(requests_.begin(), requests_.end());
576 requests_.clear(); 605 requests_.clear();
577 } 606 }
(...skipping 11 matching lines...) Expand all
589 FailureCallback failure_callback, 618 FailureCallback failure_callback,
590 const base::Time& min_update_time) { 619 const base::Time& min_update_time) {
591 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 620 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
592 DownloadContactsRequest* request = 621 DownloadContactsRequest* request =
593 new DownloadContactsRequest(this, 622 new DownloadContactsRequest(this,
594 profile_, 623 profile_,
595 runner_.get(), 624 runner_.get(),
596 success_callback, 625 success_callback,
597 failure_callback, 626 failure_callback,
598 min_update_time, 627 min_update_time,
599 max_simultaneous_photo_downloads_); 628 max_photo_downloads_per_second_);
600 VLOG(1) << "Starting contacts download with request " << request; 629 VLOG(1) << "Starting contacts download with request " << request;
601 requests_.insert(request); 630 requests_.insert(request);
602 request->Run(); 631 request->Run();
603 } 632 }
604 633
605 void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) { 634 void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) {
606 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 635 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
607 DCHECK(request); 636 DCHECK(request);
608 VLOG(1) << "Download request " << request << " complete"; 637 VLOG(1) << "Download request " << request << " complete";
609 requests_.erase(request); 638 requests_.erase(request);
610 delete request; 639 delete request;
611 } 640 }
612 641
613 } // namespace contacts 642 } // namespace contacts
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698