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; |
} |