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

Unified Diff: chrome/browser/chromeos/gdata/gdata_contacts_service.cc

Issue 10834220: contacts: Only download contacts from "My Contacts" group. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: merge and apply review feedback 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/chromeos/gdata/gdata_contacts_service.cc
diff --git a/chrome/browser/chromeos/gdata/gdata_contacts_service.cc b/chrome/browser/chromeos/gdata/gdata_contacts_service.cc
index 183de396cd9457268283021ecb90958a883a8a54..af4d65184ea5fd4d92044a431a5c2f380de8f0ec 100644
--- a/chrome/browser/chromeos/gdata/gdata_contacts_service.cc
+++ b/chrome/browser/chromeos/gdata/gdata_contacts_service.cc
@@ -9,6 +9,7 @@
#include <map>
#include <utility>
+#include "base/json/json_value_converter.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
@@ -34,6 +35,19 @@ namespace {
// At values above 10, Google starts returning 503 errors.
const int kMaxPhotoDownloadsPerSecond = 10;
+// Hardcoded system group ID for the "My Contacts" group, per
+// https://developers.google.com/google-apps/contacts/v3/#contact_group_entry.
+const char kMyContactsSystemGroupId[] = "Contacts";
+
+// Top-level field in a contact groups feed containing the list of entries.
+const char kGroupEntryField[] = "feed.entry";
+
+// Field in group entries containing the system group ID (e.g. ID "Contacts"
+// for the "My Contacts" system group). See
+// https://developers.google.com/google-apps/contacts/v3/#contact_group_entry
+// for more details.
+const char kSystemGroupIdField[] = "gContact$systemGroup.id";
+
// Field in the top-level object containing the contacts feed.
const char kFeedField[] = "feed";
@@ -50,8 +64,10 @@ const char kCategoryTermValue[] =
// Field in the contacts feed containing a list of contact entries.
const char kEntryField[] = "entry";
-// Top-level fields in contact entries.
+// Field in group and contact entries containing the item's ID.
const char kIdField[] = "id.$t";
+
+// Top-level fields in contact entries.
const char kDeletedField[] = "gd$deleted";
const char kFullNameField[] = "gd$name.gd$fullName.$t";
const char kGivenNameField[] = "gd$name.gd$givenName.$t";
@@ -306,17 +322,73 @@ bool FillContactFromDictionary(const base::DictionaryValue& dict,
return true;
}
+// Structure into which we parse the contact groups feed using
+// JSONValueConverter.
+struct ContactGroups {
+ struct ContactGroup {
+ // Group ID, e.g.
+ // "http://www.google.com/m8/feeds/groups/user%40gmail.com/base/6".
+ std::string group_id;
+
+ // System group ID (e.g. "Contacts" for the "My Contacts" system group) if
+ // this is a system group, and empty otherwise. See http://goo.gl/oWVnN
+ // for more details.
+ std::string system_group_id;
+ };
+
+ // Given a system group ID, returns the corresponding group ID or an empty
+ // string if the requested system group wasn't present.
+ std::string GetGroupIdForSystemGroup(const std::string& system_group_id) {
+ for (size_t i = 0; i < groups.size(); ++i) {
+ const ContactGroup& group = *groups[i];
+ if (group.system_group_id == system_group_id)
+ return group.group_id;
+ }
+ return std::string();
+ }
+
+ // Given |value| corresponding to a dictionary in a contact group feed's
+ // "entry" list, fills |result| with information about the group.
+ static bool GetContactGroup(const base::Value* value, ContactGroup* result) {
+ DCHECK(value);
+ DCHECK(result);
+ const base::DictionaryValue* dict = NULL;
+ if (!value->GetAsDictionary(&dict))
+ return false;
+
+ dict->GetString(kIdField, &result->group_id);
+ dict->GetString(kSystemGroupIdField, &result->system_group_id);
+ return true;
+ }
+
+ static void RegisterJSONConverter(
+ base::JSONValueConverter<ContactGroups>* converter) {
+ DCHECK(converter);
+ converter->RegisterRepeatedCustomValue<ContactGroup>(
+ kGroupEntryField, &ContactGroups::groups, &GetContactGroup);
+ }
+
+ ScopedVector<ContactGroup> groups;
+};
+
} // namespace
// This class handles a single request to download all of a user's contacts.
//
-// First, the contacts feed is downloaded via GetContactsOperation and parsed.
+// First, the feed containing the user's contact groups is downloaded via
+// GetContactGroupsOperation and examined to find the ID for the "My Contacts"
+// group (by default, the contacts API also returns suggested contacts). The
+// group ID is cached in GDataContactsService so that this step can be skipped
+// by later DownloadContactRequests.
+//
+// Next, the contacts feed is downloaded via GetContactsOperation and parsed.
// Individual contacts::Contact objects are created using the data from the
-// feed. Next, GetContactPhotoOperations are created and used to start
-// downloading contacts' photos in parallel. When all photos have been
-// downloaded, the contacts are passed to the passed-in callback.
-class GDataContactsService::DownloadContactsRequest
- : public base::SupportsWeakPtr<DownloadContactsRequest> {
+// feed.
+//
+// Finally, GetContactPhotoOperations are created and used to start downloading
+// contacts' photos in parallel. When all photos have been downloaded, the
+// contacts are passed to the passed-in callback.
+class GDataContactsService::DownloadContactsRequest {
public:
DownloadContactsRequest(GDataContactsService* service,
GDataOperationRunner* runner,
@@ -329,8 +401,11 @@ class GDataContactsService::DownloadContactsRequest
failure_callback_(failure_callback),
min_update_time_(min_update_time),
contacts_(new ScopedVector<contacts::Contact>),
+ my_contacts_group_id_(service->cached_my_contacts_group_id_),
num_in_progress_photo_downloads_(0),
- photo_download_failed_(false) {
+ photo_download_failed_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(service_);
DCHECK(runner_);
}
@@ -341,37 +416,103 @@ class GDataContactsService::DownloadContactsRequest
runner_ = NULL;
}
- // Issues the initial request to download the contact feed.
+ const std::string my_contacts_group_id() const {
+ return my_contacts_group_id_;
+ }
+
+ // Begins the contacts-downloading process. If the ID for the "My Contacts"
+ // group has previously been cached, then the contacts download is started.
+ // Otherwise, the contact groups download is started.
void Run() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!my_contacts_group_id_.empty()) {
+ StartContactsDownload();
+ } else {
+ GetContactGroupsOperation* operation =
+ new GetContactGroupsOperation(
+ runner_->operation_registry(),
+ base::Bind(&DownloadContactsRequest::HandleGroupsFeedData,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (!service_->groups_feed_url_for_testing_.is_empty()) {
+ operation->set_feed_url_for_testing(
+ service_->groups_feed_url_for_testing_);
+ }
+ runner_->StartOperationWithRetry(operation);
+ }
+ }
+
+ private:
+ // Invokes the failure callback and notifies GDataContactsService that the
+ // request is done.
+ void ReportFailure() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ failure_callback_.Run();
+ service_->OnRequestComplete(this);
+ }
+
+ // Callback for GetContactGroupsOperation calls. Starts downloading the
+ // actual contacts after finding the "My Contacts" group ID.
+ void HandleGroupsFeedData(GDataErrorCode error,
+ scoped_ptr<base::Value> feed_data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (error != HTTP_SUCCESS) {
+ LOG(WARNING) << "Got error " << error << " while downloading groups";
+ ReportFailure();
+ return;
+ }
+
+ VLOG(2) << "Got groups feed data:\n"
+ << PrettyPrintValue(*(feed_data.get()));
+ ContactGroups groups;
+ base::JSONValueConverter<ContactGroups> converter;
+ if (!converter.Convert(*feed_data, &groups)) {
+ LOG(WARNING) << "Unable to parse groups feed";
+ ReportFailure();
+ return;
+ }
+
+ my_contacts_group_id_ =
+ groups.GetGroupIdForSystemGroup(kMyContactsSystemGroupId);
+ if (!my_contacts_group_id_.empty()) {
+ StartContactsDownload();
+ } else {
+ LOG(WARNING) << "Unable to find ID for \"My Contacts\" group";
+ ReportFailure();
+ }
+ }
+
+ // Starts a download of the contacts from the "My Contacts" group.
+ void StartContactsDownload() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
GetContactsOperation* operation =
new GetContactsOperation(
runner_->operation_registry(),
+ my_contacts_group_id_,
min_update_time_,
- base::Bind(&DownloadContactsRequest::HandleFeedData,
- base::Unretained(this)));
- if (!service_->feed_url_for_testing_.is_empty())
- operation->set_feed_url_for_testing(service_->feed_url_for_testing_);
-
+ base::Bind(&DownloadContactsRequest::HandleContactsFeedData,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (!service_->contacts_feed_url_for_testing_.is_empty()) {
+ operation->set_feed_url_for_testing(
+ service_->contacts_feed_url_for_testing_);
+ }
runner_->StartOperationWithRetry(operation);
}
- private:
// Callback for GetContactsOperation calls.
- void HandleFeedData(GDataErrorCode error,
- scoped_ptr<base::Value> feed_data) {
+ void HandleContactsFeedData(GDataErrorCode error,
+ scoped_ptr<base::Value> feed_data) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (error != HTTP_SUCCESS) {
LOG(WARNING) << "Got error " << error << " while downloading contacts";
- failure_callback_.Run();
- service_->OnRequestComplete(this);
+ ReportFailure();
return;
}
- VLOG(2) << "Got feed data:\n" << PrettyPrintValue(*(feed_data.get()));
- if (!ProcessFeedData(*feed_data.get())) {
- LOG(WARNING) << "Unable to process feed data";
- failure_callback_.Run();
- service_->OnRequestComplete(this);
+ VLOG(2) << "Got contacts feed data:\n"
+ << PrettyPrintValue(*(feed_data.get()));
+ if (!ProcessContactsFeedData(*feed_data.get())) {
+ LOG(WARNING) << "Unable to process contacts feed data";
+ ReportFailure();
return;
}
@@ -384,7 +525,8 @@ class GDataContactsService::DownloadContactsRequest
// Processes the raw contacts feed from |feed_data| and fills |contacts_|.
// Returns true on success.
- bool ProcessFeedData(const base::Value& feed_data) {
+ bool ProcessContactsFeedData(const base::Value& feed_data) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
const DictionaryValue* toplevel_dict = NULL;
if (!feed_data.GetAsDictionary(&toplevel_dict)) {
LOG(WARNING) << "Top-level object is not a dictionary";
@@ -473,11 +615,12 @@ class GDataContactsService::DownloadContactsRequest
num_in_progress_photo_downloads_ == 0) {
VLOG(1) << "Done downloading photos; invoking callback";
photo_download_timer_.Stop();
- if (photo_download_failed_)
- failure_callback_.Run();
- else
+ if (photo_download_failed_) {
+ ReportFailure();
+ } else {
success_callback_.Run(contacts_.Pass());
- service_->OnRequestComplete(this);
+ service_->OnRequestComplete(this);
+ }
return;
}
}
@@ -485,6 +628,7 @@ class GDataContactsService::DownloadContactsRequest
// Starts photo downloads for contacts in |contacts_needing_photo_downloads_|.
// Should be invoked only once per second.
void StartPhotoDownloads() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
while (!contacts_needing_photo_downloads_.empty() &&
(num_in_progress_photo_downloads_ <
service_->max_photo_downloads_per_second_)) {
@@ -500,7 +644,8 @@ class GDataContactsService::DownloadContactsRequest
runner_->operation_registry(),
GURL(url),
base::Bind(&DownloadContactsRequest::HandlePhotoData,
- AsWeakPtr(), contact)));
+ weak_ptr_factory_.GetWeakPtr(),
+ contact)));
num_in_progress_photo_downloads_++;
}
}
@@ -544,7 +689,6 @@ class GDataContactsService::DownloadContactsRequest
CheckCompletion();
}
- private:
typedef std::map<contacts::Contact*, std::string> ContactPhotoUrls;
GDataContactsService* service_; // not owned
@@ -557,6 +701,9 @@ class GDataContactsService::DownloadContactsRequest
scoped_ptr<ScopedVector<contacts::Contact> > contacts_;
+ // ID of the "My Contacts" contacts group.
+ std::string my_contacts_group_id_;
+
// Map from a contact to the URL at which its photo is located.
// Contacts without photos do not appear in this map.
ContactPhotoUrls contact_photo_urls_;
@@ -574,6 +721,10 @@ class GDataContactsService::DownloadContactsRequest
// Did we encounter a fatal error while downloading a photo?
bool photo_download_failed_;
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<DownloadContactsRequest> weak_ptr_factory_;
+
DISALLOW_COPY_AND_ASSIGN(DownloadContactsRequest);
};
@@ -619,6 +770,8 @@ void GDataContactsService::OnRequestComplete(DownloadContactsRequest* request) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(request);
VLOG(1) << "Download request " << request << " complete";
+ if (!request->my_contacts_group_id().empty())
+ cached_my_contacts_group_id_ = request->my_contacts_group_id();
requests_.erase(request);
delete request;
}

Powered by Google App Engine
This is Rietveld 408576698