OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/gaia_userinfo/profile_image_downloader.h" |
| 6 |
| 7 #include <string> |
| 8 #include <vector> |
| 9 |
| 10 #include "base/json/json_reader.h" |
| 11 #include "base/logging.h" |
| 12 #include "base/utf_string_conversions.h" |
| 13 #include "base/message_loop.h" |
| 14 #include "base/string_split.h" |
| 15 #include "base/string_util.h" |
| 16 #include "base/stringprintf.h" |
| 17 #include "chrome/browser/net/gaia/token_service.h" |
| 18 #include "chrome/browser/profiles/profile.h" |
| 19 #include "chrome/common/chrome_notification_types.h" |
| 20 #include "chrome/common/net/gaia/gaia_constants.h" |
| 21 #include "content/public/browser/browser_thread.h" |
| 22 #include "content/public/browser/notification_details.h" |
| 23 #include "content/public/browser/notification_observer.h" |
| 24 #include "content/public/browser/notification_registrar.h" |
| 25 #include "content/public/browser/notification_source.h" |
| 26 #include "content/public/browser/notification_types.h" |
| 27 #include "content/public/common/url_fetcher.h" |
| 28 #include "googleurl/src/gurl.h" |
| 29 #include "skia/ext/image_operations.h" |
| 30 #include "third_party/skia/include/core/SkBitmap.h" |
| 31 |
| 32 using content::BrowserThread; |
| 33 |
| 34 namespace { |
| 35 |
| 36 // Template for optional authorization header. |
| 37 const char kAuthorizationHeader[] = "Authorization: GoogleLogin auth=%s"; |
| 38 |
| 39 // URL requesting Picasa API for user info. |
| 40 const char kUserEntryURL[] = |
| 41 "http://picasaweb.google.com/data/entry/api/user/default?alt=json"; |
| 42 // Path in JSON dictionary to user's photo thumbnail URL. |
| 43 const char kPhotoThumbnailURLPath[] = "entry.gphoto$thumbnail.$t"; |
| 44 |
| 45 const char kFullNamePath[] = "entry.gphoto$nickname.$t"; |
| 46 |
| 47 // Path format for specifying thumbnail's size. |
| 48 const char kThumbnailSizeFormat[] = "s%d-c"; |
| 49 // Default Picasa thumbnail size. |
| 50 const int kDefaultThumbnailSize = 64; |
| 51 |
| 52 // Separator of URL path components. |
| 53 const char kURLPathSeparator = '/'; |
| 54 |
| 55 // Photo ID of the Picasa Web Albums profile picture (base64 of 0). |
| 56 const char kPicasaPhotoId[] = "AAAAAAAAAAA"; |
| 57 |
| 58 // Photo version of the default PWA profile picture (base64 of 1). |
| 59 const char kDefaultPicasaPhotoVersion[] = "AAAAAAAAAAE"; |
| 60 |
| 61 // Photo ID of the Google+ profile picture (base64 of 2). |
| 62 const char kGooglePlusPhotoId[] = "AAAAAAAAAAI"; |
| 63 |
| 64 // Photo version of the default Google+ profile picture (base64 of 0). |
| 65 const char kDefaultGooglePlusPhotoVersion[] = "AAAAAAAAAAA"; |
| 66 |
| 67 // Number of path components in profile picture URL. |
| 68 const size_t kProfileImageURLPathComponentsCount = 7; |
| 69 |
| 70 // Index of path component with photo ID. |
| 71 const int kPhotoIdPathComponentIndex = 2; |
| 72 |
| 73 // Index of path component with photo version. |
| 74 const int kPhotoVersionPathComponentIndex = 3; |
| 75 |
| 76 } // namespace |
| 77 |
| 78 std::string ProfileImageDownloader::GetProfileImageURL( |
| 79 const std::string& data) { |
| 80 int error_code = -1; |
| 81 std::string error_message; |
| 82 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( |
| 83 data, false, &error_code, &error_message)); |
| 84 if (!root_value.get()) { |
| 85 LOG(ERROR) << "Error while parsing Picasa user entry response: " |
| 86 << error_message; |
| 87 return std::string(); |
| 88 } |
| 89 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { |
| 90 LOG(ERROR) << "JSON root is not a dictionary: " |
| 91 << root_value->GetType(); |
| 92 return std::string(); |
| 93 } |
| 94 base::DictionaryValue* root_dictionary = |
| 95 static_cast<base::DictionaryValue*>(root_value.get()); |
| 96 |
| 97 std::string thumbnail_url_string; |
| 98 if (!root_dictionary->GetString( |
| 99 kPhotoThumbnailURLPath, &thumbnail_url_string)) { |
| 100 LOG(ERROR) << "Can't find thumbnail path in JSON data: " |
| 101 << data; |
| 102 return std::string(); |
| 103 } |
| 104 |
| 105 if (!root_dictionary->GetString(kFullNamePath, &full_name_)) { |
| 106 } else { |
| 107 } |
| 108 |
| 109 fprintf(stderr, "%s - got name: %s\n", |
| 110 __func__, UTF16ToUTF8(full_name_).c_str()); |
| 111 |
| 112 // Try to change the size of thumbnail we are going to get. |
| 113 // Typical URL looks like this: |
| 114 // http://lh0.ggpht.com/-abcd1aBCDEf/AAAA/AAA_A/abc12/s64-c/1234567890.jpg |
| 115 std::string default_thumbnail_size_path_component( |
| 116 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize)); |
| 117 std::string new_thumbnail_size_path_component( |
| 118 base::StringPrintf(kThumbnailSizeFormat, |
| 119 delegate_->GetDesiredImageSize())); |
| 120 size_t thumbnail_size_pos = |
| 121 thumbnail_url_string.find(default_thumbnail_size_path_component); |
| 122 if (thumbnail_size_pos != std::string::npos) { |
| 123 size_t thumbnail_size_end = |
| 124 thumbnail_size_pos + default_thumbnail_size_path_component.size(); |
| 125 thumbnail_url_string = |
| 126 thumbnail_url_string.substr(0, thumbnail_size_pos) + |
| 127 new_thumbnail_size_path_component + |
| 128 thumbnail_url_string.substr( |
| 129 thumbnail_size_end, |
| 130 thumbnail_url_string.size() - thumbnail_size_end); |
| 131 } else { |
| 132 LOG(WARNING) << "Hasn't found thumbnail size part in image URL: " |
| 133 << thumbnail_url_string; |
| 134 // Use the thumbnail URL we have. |
| 135 } |
| 136 |
| 137 fprintf(stderr, "%s - thumbnail URL is : %s\n", |
| 138 __func__, thumbnail_url_string.c_str()); |
| 139 |
| 140 GURL thumbnail_url(thumbnail_url_string); |
| 141 if (!thumbnail_url.is_valid()) { |
| 142 LOG(ERROR) << "Thumbnail URL is not valid: " << thumbnail_url_string; |
| 143 return std::string(); |
| 144 } |
| 145 return thumbnail_url.spec(); |
| 146 } |
| 147 |
| 148 bool ProfileImageDownloader::IsDefaultProfileImageURL( |
| 149 const std::string& url) const { |
| 150 |
| 151 GURL image_url_object(url); |
| 152 DCHECK(image_url_object.is_valid()); |
| 153 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); |
| 154 std::vector<std::string> path_components; |
| 155 base::SplitString(image_url_object.path(), |
| 156 kURLPathSeparator, |
| 157 &path_components); |
| 158 |
| 159 if (path_components.size() != kProfileImageURLPathComponentsCount) |
| 160 return false; |
| 161 |
| 162 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; |
| 163 const std::string& photo_version = |
| 164 path_components[kPhotoVersionPathComponentIndex]; |
| 165 |
| 166 // There are at least two pairs of (ID, version) for the default photo: |
| 167 // the default Google+ profile photo and the default Picasa profile photo. |
| 168 return ((photo_id == kPicasaPhotoId && |
| 169 photo_version == kDefaultPicasaPhotoVersion) || |
| 170 (photo_id == kGooglePlusPhotoId && |
| 171 photo_version == kDefaultGooglePlusPhotoVersion)); |
| 172 } |
| 173 |
| 174 ProfileImageDownloader::ProfileImageDownloader(Delegate* delegate) |
| 175 : delegate_(delegate) { |
| 176 DCHECK(delegate_); |
| 177 } |
| 178 |
| 179 void ProfileImageDownloader::Start() { |
| 180 VLOG(1) << "Starting profile image downloader..."; |
| 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 182 |
| 183 TokenService* service = |
| 184 delegate_->GetProfile()->GetTokenService(); |
| 185 if (!service) { |
| 186 // This can happen in some test paths. |
| 187 fprintf(stderr, "%s no token service\n", __func__); |
| 188 LOG(WARNING) << "User has no token service"; |
| 189 delegate_->OnDownloadFailure(); |
| 190 return; |
| 191 } |
| 192 if (service->HasTokenForService(GaiaConstants::kPicasaService)) { |
| 193 auth_token_ = |
| 194 service->GetTokenForService(GaiaConstants::kPicasaService); |
| 195 StartFetchingImage(); |
| 196 } else { |
| 197 fprintf(stderr, "%s does not have picasa token, waiting\n", __func__); |
| 198 } |
| 199 |
| 200 registrar_.Add(this, |
| 201 chrome::NOTIFICATION_TOKEN_AVAILABLE, |
| 202 content::Source<TokenService>(service)); |
| 203 registrar_.Add(this, |
| 204 chrome::NOTIFICATION_TOKEN_REQUEST_FAILED, |
| 205 content::Source<TokenService>(service)); |
| 206 } |
| 207 |
| 208 void ProfileImageDownloader::StartFetchingImage() { |
| 209 std::string email = delegate_->GetProfileUserName(); |
| 210 if (email.empty()) |
| 211 return; |
| 212 VLOG(1) << "Fetching user entry with token: " << auth_token_; |
| 213 user_entry_fetcher_.reset(content::URLFetcher::Create( |
| 214 GURL(kUserEntryURL), content::URLFetcher::GET, this)); |
| 215 user_entry_fetcher_->SetRequestContext( |
| 216 delegate_->GetProfile()->GetRequestContext()); |
| 217 if (!auth_token_.empty()) { |
| 218 user_entry_fetcher_->SetExtraRequestHeaders( |
| 219 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); |
| 220 } |
| 221 user_entry_fetcher_->Start(); |
| 222 } |
| 223 |
| 224 ProfileImageDownloader::~ProfileImageDownloader() {} |
| 225 |
| 226 void ProfileImageDownloader::OnURLFetchComplete( |
| 227 const content::URLFetcher* source) { |
| 228 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 229 std::string data; |
| 230 source->GetResponseAsString(&data); |
| 231 if (source->GetResponseCode() != 200) { |
| 232 fprintf(stderr, "%s, URL fetch error\n", __func__); |
| 233 LOG(ERROR) << "Response code is " << source->GetResponseCode(); |
| 234 LOG(ERROR) << "Url is " << source->GetURL().spec(); |
| 235 LOG(ERROR) << "Data is " << data; |
| 236 delegate_->OnDownloadFailure(); |
| 237 |
| 238 /* |
| 239 if (source->GetResponseCode() == 403) { |
| 240 TokenService* service = |
| 241 delegate_->GetProfile()->GetTokenService(); |
| 242 service->ClearTokenForService(GaiaConstants::kPicasaService); |
| 243 service->StartFetchingMissingTokens(); |
| 244 } |
| 245 */ |
| 246 |
| 247 return; |
| 248 } |
| 249 |
| 250 if (source == user_entry_fetcher_.get()) { |
| 251 std::string image_url = GetProfileImageURL(data); |
| 252 if (image_url.empty()) { |
| 253 delegate_->OnDownloadFailure(); |
| 254 return; |
| 255 } |
| 256 if (IsDefaultProfileImageURL(image_url)) { |
| 257 delegate_->OnDownloadDefaultImage(); |
| 258 return; |
| 259 } |
| 260 VLOG(1) << "Fetching profile image from " << image_url; |
| 261 profile_image_fetcher_.reset(content::URLFetcher::Create( |
| 262 GURL(image_url), content::URLFetcher::GET, this)); |
| 263 profile_image_fetcher_->SetRequestContext( |
| 264 delegate_->GetProfile()->GetRequestContext()); |
| 265 if (!auth_token_.empty()) { |
| 266 profile_image_fetcher_->SetExtraRequestHeaders( |
| 267 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); |
| 268 } |
| 269 profile_image_fetcher_->Start(); |
| 270 } else if (source == profile_image_fetcher_.get()) { |
| 271 VLOG(1) << "Decoding the image..."; |
| 272 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder( |
| 273 this, data); |
| 274 image_decoder->Start(); |
| 275 } |
| 276 } |
| 277 |
| 278 void ProfileImageDownloader::OnImageDecoded(const ImageDecoder* decoder, |
| 279 const SkBitmap& decoded_image) { |
| 280 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 281 SkBitmap resized_image = skia::ImageOperations::Resize( |
| 282 decoded_image, |
| 283 skia::ImageOperations::RESIZE_BEST, |
| 284 delegate_->GetDesiredImageSize(), |
| 285 delegate_->GetDesiredImageSize()); |
| 286 delegate_->OnDownloadSuccess(resized_image, full_name_); |
| 287 } |
| 288 |
| 289 void ProfileImageDownloader::OnDecodeImageFailed(const ImageDecoder* decoder) { |
| 290 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 291 delegate_->OnDownloadFailure(); |
| 292 } |
| 293 |
| 294 void ProfileImageDownloader::Observe( |
| 295 int type, |
| 296 const content::NotificationSource& source, |
| 297 const content::NotificationDetails& details) { |
| 298 DCHECK(type == chrome::NOTIFICATION_TOKEN_AVAILABLE || |
| 299 type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED); |
| 300 |
| 301 fprintf(stderr, "%s\n", __func__); |
| 302 |
| 303 TokenService::TokenAvailableDetails* token_details = |
| 304 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); |
| 305 if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) { |
| 306 if (token_details->service() == GaiaConstants::kPicasaService) { |
| 307 registrar_.RemoveAll(); |
| 308 auth_token_ = token_details->token(); |
| 309 StartFetchingImage(); |
| 310 } |
| 311 } else { |
| 312 if (token_details->service() == GaiaConstants::kPicasaService) { |
| 313 LOG(WARNING) << "ProfileImageDownloader: token request failed"; |
| 314 delegate_->OnDownloadFailure(); |
| 315 } |
| 316 } |
| 317 } |
OLD | NEW |