| Index: chrome/browser/gaia_userinfo/profile_image_downloader.cc
|
| diff --git a/chrome/browser/gaia_userinfo/profile_image_downloader.cc b/chrome/browser/gaia_userinfo/profile_image_downloader.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ad2343636d4b8864faca7ffdd08c1a64a55eac79
|
| --- /dev/null
|
| +++ b/chrome/browser/gaia_userinfo/profile_image_downloader.cc
|
| @@ -0,0 +1,317 @@
|
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/gaia_userinfo/profile_image_downloader.h"
|
| +
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/json/json_reader.h"
|
| +#include "base/logging.h"
|
| +#include "base/utf_string_conversions.h"
|
| +#include "base/message_loop.h"
|
| +#include "base/string_split.h"
|
| +#include "base/string_util.h"
|
| +#include "base/stringprintf.h"
|
| +#include "chrome/browser/net/gaia/token_service.h"
|
| +#include "chrome/browser/profiles/profile.h"
|
| +#include "chrome/common/chrome_notification_types.h"
|
| +#include "chrome/common/net/gaia/gaia_constants.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/browser/notification_details.h"
|
| +#include "content/public/browser/notification_observer.h"
|
| +#include "content/public/browser/notification_registrar.h"
|
| +#include "content/public/browser/notification_source.h"
|
| +#include "content/public/browser/notification_types.h"
|
| +#include "content/public/common/url_fetcher.h"
|
| +#include "googleurl/src/gurl.h"
|
| +#include "skia/ext/image_operations.h"
|
| +#include "third_party/skia/include/core/SkBitmap.h"
|
| +
|
| +using content::BrowserThread;
|
| +
|
| +namespace {
|
| +
|
| +// Template for optional authorization header.
|
| +const char kAuthorizationHeader[] = "Authorization: GoogleLogin auth=%s";
|
| +
|
| +// URL requesting Picasa API for user info.
|
| +const char kUserEntryURL[] =
|
| + "http://picasaweb.google.com/data/entry/api/user/default?alt=json";
|
| +// Path in JSON dictionary to user's photo thumbnail URL.
|
| +const char kPhotoThumbnailURLPath[] = "entry.gphoto$thumbnail.$t";
|
| +
|
| +const char kFullNamePath[] = "entry.gphoto$nickname.$t";
|
| +
|
| +// Path format for specifying thumbnail's size.
|
| +const char kThumbnailSizeFormat[] = "s%d-c";
|
| +// Default Picasa thumbnail size.
|
| +const int kDefaultThumbnailSize = 64;
|
| +
|
| +// Separator of URL path components.
|
| +const char kURLPathSeparator = '/';
|
| +
|
| +// Photo ID of the Picasa Web Albums profile picture (base64 of 0).
|
| +const char kPicasaPhotoId[] = "AAAAAAAAAAA";
|
| +
|
| +// Photo version of the default PWA profile picture (base64 of 1).
|
| +const char kDefaultPicasaPhotoVersion[] = "AAAAAAAAAAE";
|
| +
|
| +// Photo ID of the Google+ profile picture (base64 of 2).
|
| +const char kGooglePlusPhotoId[] = "AAAAAAAAAAI";
|
| +
|
| +// Photo version of the default Google+ profile picture (base64 of 0).
|
| +const char kDefaultGooglePlusPhotoVersion[] = "AAAAAAAAAAA";
|
| +
|
| +// Number of path components in profile picture URL.
|
| +const size_t kProfileImageURLPathComponentsCount = 7;
|
| +
|
| +// Index of path component with photo ID.
|
| +const int kPhotoIdPathComponentIndex = 2;
|
| +
|
| +// Index of path component with photo version.
|
| +const int kPhotoVersionPathComponentIndex = 3;
|
| +
|
| +} // namespace
|
| +
|
| +std::string ProfileImageDownloader::GetProfileImageURL(
|
| + const std::string& data) {
|
| + int error_code = -1;
|
| + std::string error_message;
|
| + scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError(
|
| + data, false, &error_code, &error_message));
|
| + if (!root_value.get()) {
|
| + LOG(ERROR) << "Error while parsing Picasa user entry response: "
|
| + << error_message;
|
| + return std::string();
|
| + }
|
| + if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) {
|
| + LOG(ERROR) << "JSON root is not a dictionary: "
|
| + << root_value->GetType();
|
| + return std::string();
|
| + }
|
| + base::DictionaryValue* root_dictionary =
|
| + static_cast<base::DictionaryValue*>(root_value.get());
|
| +
|
| + std::string thumbnail_url_string;
|
| + if (!root_dictionary->GetString(
|
| + kPhotoThumbnailURLPath, &thumbnail_url_string)) {
|
| + LOG(ERROR) << "Can't find thumbnail path in JSON data: "
|
| + << data;
|
| + return std::string();
|
| + }
|
| +
|
| + if (!root_dictionary->GetString(kFullNamePath, &full_name_)) {
|
| + } else {
|
| + }
|
| +
|
| + fprintf(stderr, "%s - got name: %s\n",
|
| + __func__, UTF16ToUTF8(full_name_).c_str());
|
| +
|
| + // Try to change the size of thumbnail we are going to get.
|
| + // Typical URL looks like this:
|
| + // http://lh0.ggpht.com/-abcd1aBCDEf/AAAA/AAA_A/abc12/s64-c/1234567890.jpg
|
| + std::string default_thumbnail_size_path_component(
|
| + base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize));
|
| + std::string new_thumbnail_size_path_component(
|
| + base::StringPrintf(kThumbnailSizeFormat,
|
| + delegate_->GetDesiredImageSize()));
|
| + size_t thumbnail_size_pos =
|
| + thumbnail_url_string.find(default_thumbnail_size_path_component);
|
| + if (thumbnail_size_pos != std::string::npos) {
|
| + size_t thumbnail_size_end =
|
| + thumbnail_size_pos + default_thumbnail_size_path_component.size();
|
| + thumbnail_url_string =
|
| + thumbnail_url_string.substr(0, thumbnail_size_pos) +
|
| + new_thumbnail_size_path_component +
|
| + thumbnail_url_string.substr(
|
| + thumbnail_size_end,
|
| + thumbnail_url_string.size() - thumbnail_size_end);
|
| + } else {
|
| + LOG(WARNING) << "Hasn't found thumbnail size part in image URL: "
|
| + << thumbnail_url_string;
|
| + // Use the thumbnail URL we have.
|
| + }
|
| +
|
| + fprintf(stderr, "%s - thumbnail URL is : %s\n",
|
| + __func__, thumbnail_url_string.c_str());
|
| +
|
| + GURL thumbnail_url(thumbnail_url_string);
|
| + if (!thumbnail_url.is_valid()) {
|
| + LOG(ERROR) << "Thumbnail URL is not valid: " << thumbnail_url_string;
|
| + return std::string();
|
| + }
|
| + return thumbnail_url.spec();
|
| +}
|
| +
|
| +bool ProfileImageDownloader::IsDefaultProfileImageURL(
|
| + const std::string& url) const {
|
| +
|
| + GURL image_url_object(url);
|
| + DCHECK(image_url_object.is_valid());
|
| + VLOG(1) << "URL to check for default image: " << image_url_object.spec();
|
| + std::vector<std::string> path_components;
|
| + base::SplitString(image_url_object.path(),
|
| + kURLPathSeparator,
|
| + &path_components);
|
| +
|
| + if (path_components.size() != kProfileImageURLPathComponentsCount)
|
| + return false;
|
| +
|
| + const std::string& photo_id = path_components[kPhotoIdPathComponentIndex];
|
| + const std::string& photo_version =
|
| + path_components[kPhotoVersionPathComponentIndex];
|
| +
|
| + // There are at least two pairs of (ID, version) for the default photo:
|
| + // the default Google+ profile photo and the default Picasa profile photo.
|
| + return ((photo_id == kPicasaPhotoId &&
|
| + photo_version == kDefaultPicasaPhotoVersion) ||
|
| + (photo_id == kGooglePlusPhotoId &&
|
| + photo_version == kDefaultGooglePlusPhotoVersion));
|
| +}
|
| +
|
| +ProfileImageDownloader::ProfileImageDownloader(Delegate* delegate)
|
| + : delegate_(delegate) {
|
| + DCHECK(delegate_);
|
| +}
|
| +
|
| +void ProfileImageDownloader::Start() {
|
| + VLOG(1) << "Starting profile image downloader...";
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| +
|
| + TokenService* service =
|
| + delegate_->GetProfile()->GetTokenService();
|
| + if (!service) {
|
| + // This can happen in some test paths.
|
| + fprintf(stderr, "%s no token service\n", __func__);
|
| + LOG(WARNING) << "User has no token service";
|
| + delegate_->OnDownloadFailure();
|
| + return;
|
| + }
|
| + if (service->HasTokenForService(GaiaConstants::kPicasaService)) {
|
| + auth_token_ =
|
| + service->GetTokenForService(GaiaConstants::kPicasaService);
|
| + StartFetchingImage();
|
| + } else {
|
| + fprintf(stderr, "%s does not have picasa token, waiting\n", __func__);
|
| + }
|
| +
|
| + registrar_.Add(this,
|
| + chrome::NOTIFICATION_TOKEN_AVAILABLE,
|
| + content::Source<TokenService>(service));
|
| + registrar_.Add(this,
|
| + chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
|
| + content::Source<TokenService>(service));
|
| +}
|
| +
|
| +void ProfileImageDownloader::StartFetchingImage() {
|
| + std::string email = delegate_->GetProfileUserName();
|
| + if (email.empty())
|
| + return;
|
| + VLOG(1) << "Fetching user entry with token: " << auth_token_;
|
| + user_entry_fetcher_.reset(content::URLFetcher::Create(
|
| + GURL(kUserEntryURL), content::URLFetcher::GET, this));
|
| + user_entry_fetcher_->SetRequestContext(
|
| + delegate_->GetProfile()->GetRequestContext());
|
| + if (!auth_token_.empty()) {
|
| + user_entry_fetcher_->SetExtraRequestHeaders(
|
| + base::StringPrintf(kAuthorizationHeader, auth_token_.c_str()));
|
| + }
|
| + user_entry_fetcher_->Start();
|
| +}
|
| +
|
| +ProfileImageDownloader::~ProfileImageDownloader() {}
|
| +
|
| +void ProfileImageDownloader::OnURLFetchComplete(
|
| + const content::URLFetcher* source) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + std::string data;
|
| + source->GetResponseAsString(&data);
|
| + if (source->GetResponseCode() != 200) {
|
| + fprintf(stderr, "%s, URL fetch error\n", __func__);
|
| + LOG(ERROR) << "Response code is " << source->GetResponseCode();
|
| + LOG(ERROR) << "Url is " << source->GetURL().spec();
|
| + LOG(ERROR) << "Data is " << data;
|
| + delegate_->OnDownloadFailure();
|
| +
|
| +/*
|
| + if (source->GetResponseCode() == 403) {
|
| + TokenService* service =
|
| + delegate_->GetProfile()->GetTokenService();
|
| + service->ClearTokenForService(GaiaConstants::kPicasaService);
|
| + service->StartFetchingMissingTokens();
|
| + }
|
| +*/
|
| +
|
| + return;
|
| + }
|
| +
|
| + if (source == user_entry_fetcher_.get()) {
|
| + std::string image_url = GetProfileImageURL(data);
|
| + if (image_url.empty()) {
|
| + delegate_->OnDownloadFailure();
|
| + return;
|
| + }
|
| + if (IsDefaultProfileImageURL(image_url)) {
|
| + delegate_->OnDownloadDefaultImage();
|
| + return;
|
| + }
|
| + VLOG(1) << "Fetching profile image from " << image_url;
|
| + profile_image_fetcher_.reset(content::URLFetcher::Create(
|
| + GURL(image_url), content::URLFetcher::GET, this));
|
| + profile_image_fetcher_->SetRequestContext(
|
| + delegate_->GetProfile()->GetRequestContext());
|
| + if (!auth_token_.empty()) {
|
| + profile_image_fetcher_->SetExtraRequestHeaders(
|
| + base::StringPrintf(kAuthorizationHeader, auth_token_.c_str()));
|
| + }
|
| + profile_image_fetcher_->Start();
|
| + } else if (source == profile_image_fetcher_.get()) {
|
| + VLOG(1) << "Decoding the image...";
|
| + scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder(
|
| + this, data);
|
| + image_decoder->Start();
|
| + }
|
| +}
|
| +
|
| +void ProfileImageDownloader::OnImageDecoded(const ImageDecoder* decoder,
|
| + const SkBitmap& decoded_image) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + SkBitmap resized_image = skia::ImageOperations::Resize(
|
| + decoded_image,
|
| + skia::ImageOperations::RESIZE_BEST,
|
| + delegate_->GetDesiredImageSize(),
|
| + delegate_->GetDesiredImageSize());
|
| + delegate_->OnDownloadSuccess(resized_image, full_name_);
|
| +}
|
| +
|
| +void ProfileImageDownloader::OnDecodeImageFailed(const ImageDecoder* decoder) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + delegate_->OnDownloadFailure();
|
| +}
|
| +
|
| +void ProfileImageDownloader::Observe(
|
| + int type,
|
| + const content::NotificationSource& source,
|
| + const content::NotificationDetails& details) {
|
| + DCHECK(type == chrome::NOTIFICATION_TOKEN_AVAILABLE ||
|
| + type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED);
|
| +
|
| + fprintf(stderr, "%s\n", __func__);
|
| +
|
| + TokenService::TokenAvailableDetails* token_details =
|
| + content::Details<TokenService::TokenAvailableDetails>(details).ptr();
|
| + if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) {
|
| + if (token_details->service() == GaiaConstants::kPicasaService) {
|
| + registrar_.RemoveAll();
|
| + auth_token_ = token_details->token();
|
| + StartFetchingImage();
|
| + }
|
| + } else {
|
| + if (token_details->service() == GaiaConstants::kPicasaService) {
|
| + LOG(WARNING) << "ProfileImageDownloader: token request failed";
|
| + delegate_->OnDownloadFailure();
|
| + }
|
| + }
|
| +}
|
|
|