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 3 // found in the LICENSE file. 4 5 #include "chrome/browser/drive/gdata_wapi_service.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/values.h" 13 #include "chrome/browser/drive/drive_api_util.h" 14 #include "chrome/browser/google_apis/auth_service.h" 15 #include "chrome/browser/google_apis/drive_api_parser.h" 16 #include "chrome/browser/google_apis/gdata_errorcode.h" 17 #include "chrome/browser/google_apis/gdata_wapi_parser.h" 18 #include "chrome/browser/google_apis/gdata_wapi_requests.h" 19 #include "chrome/browser/google_apis/gdata_wapi_url_generator.h" 20 #include "chrome/browser/google_apis/request_sender.h" 21 #include "content/public/browser/browser_thread.h" 22 23 using content::BrowserThread; 24 using google_apis::AboutResource; 25 using google_apis::AccountMetadata; 26 using google_apis::AddResourceToDirectoryRequest; 27 using google_apis::AppList; 28 using google_apis::AuthStatusCallback; 29 using google_apis::AuthorizeAppCallback; 30 using google_apis::AuthorizeAppRequest; 31 using google_apis::AuthService; 32 using google_apis::CancelCallback; 33 using google_apis::CopyHostedDocumentRequest; 34 using google_apis::CreateDirectoryRequest; 35 using google_apis::DeleteResourceRequest; 36 using google_apis::DownloadActionCallback; 37 using google_apis::DownloadFileRequest; 38 using google_apis::EntryActionCallback; 39 using google_apis::GDataErrorCode; 40 using google_apis::GDATA_PARSE_ERROR; 41 using google_apis::GetAboutResourceCallback; 42 using google_apis::GetAccountMetadataRequest; 43 using google_apis::GetAppListCallback; 44 using google_apis::GetContentCallback; 45 using google_apis::GetResourceEntryCallback; 46 using google_apis::GetResourceEntryRequest; 47 using google_apis::GetResourceListCallback; 48 using google_apis::GetResourceListRequest; 49 using google_apis::GetShareUrlCallback; 50 using google_apis::GetUploadStatusRequest; 51 using google_apis::HTTP_NOT_IMPLEMENTED; 52 using google_apis::InitiateUploadCallback; 53 using google_apis::InitiateUploadExistingFileRequest; 54 using google_apis::InitiateUploadNewFileRequest; 55 using google_apis::Link; 56 using google_apis::ProgressCallback; 57 using google_apis::RemoveResourceFromDirectoryRequest; 58 using google_apis::RenameResourceRequest; 59 using google_apis::RequestSender; 60 using google_apis::ResourceEntry; 61 using google_apis::ResumeUploadRequest; 62 using google_apis::SearchByTitleRequest; 63 using google_apis::UploadRangeCallback; 64 65 namespace drive { 66 67 namespace { 68 69 // OAuth2 scopes for the documents API. 70 const char kSpreadsheetsScope[] = "https://spreadsheets.google.com/feeds/"; 71 const char kUserContentScope[] = "https://docs.googleusercontent.com/"; 72 73 // The resource ID for the root directory for WAPI is defined in the spec: 74 // https://developers.google.com/google-apps/documents-list/ 75 const char kWapiRootDirectoryResourceId[] = "folder:root"; 76 77 // Parses the JSON value to ResourceEntry runs |callback|. 78 void ParseResourceEntryAndRun(const GetResourceEntryCallback& callback, 79 GDataErrorCode error, 80 scoped_ptr<base::Value> value) { 81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 82 83 if (!value) { 84 callback.Run(error, scoped_ptr<ResourceEntry>()); 85 return; 86 } 87 88 // Parsing ResourceEntry is cheap enough to do on UI thread. 89 scoped_ptr<ResourceEntry> entry = 90 google_apis::ResourceEntry::ExtractAndParse(*value); 91 if (!entry) { 92 callback.Run(GDATA_PARSE_ERROR, scoped_ptr<ResourceEntry>()); 93 return; 94 } 95 96 callback.Run(error, entry.Pass()); 97 } 98 99 void ParseAboutResourceAndRun( 100 const GetAboutResourceCallback& callback, 101 GDataErrorCode error, 102 scoped_ptr<AccountMetadata> account_metadata) { 103 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 104 DCHECK(!callback.is_null()); 105 106 scoped_ptr<AboutResource> about_resource; 107 if (account_metadata) { 108 about_resource = AboutResource::CreateFromAccountMetadata( 109 *account_metadata, kWapiRootDirectoryResourceId); 110 } 111 112 callback.Run(error, about_resource.Pass()); 113 } 114 115 void ParseAppListAndRun( 116 const GetAppListCallback& callback, 117 GDataErrorCode error, 118 scoped_ptr<AccountMetadata> account_metadata) { 119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 120 DCHECK(!callback.is_null()); 121 122 scoped_ptr<AppList> app_list; 123 if (account_metadata) { 124 app_list = AppList::CreateFromAccountMetadata(*account_metadata); 125 } 126 127 callback.Run(error, app_list.Pass()); 128 } 129 130 } // namespace 131 132 GDataWapiService::GDataWapiService( 133 OAuth2TokenService* oauth2_token_service, 134 net::URLRequestContextGetter* url_request_context_getter, 135 base::TaskRunner* blocking_task_runner, 136 const GURL& base_url, 137 const GURL& base_download_url, 138 const std::string& custom_user_agent) 139 : oauth2_token_service_(oauth2_token_service), 140 url_request_context_getter_(url_request_context_getter), 141 blocking_task_runner_(blocking_task_runner), 142 url_generator_(base_url, base_download_url), 143 custom_user_agent_(custom_user_agent) { 144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 145 } 146 147 GDataWapiService::~GDataWapiService() { 148 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 149 if (sender_.get()) 150 sender_->auth_service()->RemoveObserver(this); 151 } 152 153 void GDataWapiService::Initialize() { 154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 155 156 std::vector<std::string> scopes; 157 scopes.push_back(util::kDocsListScope); 158 scopes.push_back(kSpreadsheetsScope); 159 scopes.push_back(kUserContentScope); 160 // Drive App scope is required for even WAPI v3 apps access. 161 scopes.push_back(util::kDriveAppsScope); 162 sender_.reset(new RequestSender( 163 new AuthService( 164 oauth2_token_service_, url_request_context_getter_, scopes), 165 url_request_context_getter_, 166 blocking_task_runner_.get(), 167 custom_user_agent_)); 168 169 sender_->auth_service()->AddObserver(this); 170 } 171 172 void GDataWapiService::AddObserver(DriveServiceObserver* observer) { 173 observers_.AddObserver(observer); 174 } 175 176 void GDataWapiService::RemoveObserver(DriveServiceObserver* observer) { 177 observers_.RemoveObserver(observer); 178 } 179 180 bool GDataWapiService::CanSendRequest() const { 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 182 183 return HasRefreshToken(); 184 } 185 186 std::string GDataWapiService::CanonicalizeResourceId( 187 const std::string& resource_id) const { 188 return resource_id; 189 } 190 191 std::string GDataWapiService::GetRootResourceId() const { 192 return kWapiRootDirectoryResourceId; 193 } 194 195 // Because GData WAPI support is expected to be gone somehow soon by migration 196 // to the Drive API v2, so we'll reuse GetResourceListRequest to implement 197 // following methods, instead of cleaning the request class. 198 199 CancelCallback GDataWapiService::GetAllResourceList( 200 const GetResourceListCallback& callback) { 201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 202 DCHECK(!callback.is_null()); 203 204 return sender_->StartRequestWithRetry( 205 new GetResourceListRequest(sender_.get(), 206 url_generator_, 207 GURL(), // No override url 208 0, // start changestamp 209 std::string(), // empty search query 210 std::string(), // no directory resource id 211 callback)); 212 } 213 214 CancelCallback GDataWapiService::GetResourceListInDirectory( 215 const std::string& directory_resource_id, 216 const GetResourceListCallback& callback) { 217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 218 DCHECK(!directory_resource_id.empty()); 219 DCHECK(!callback.is_null()); 220 221 return sender_->StartRequestWithRetry( 222 new GetResourceListRequest(sender_.get(), 223 url_generator_, 224 GURL(), // No override url 225 0, // start changestamp 226 std::string(), // empty search query 227 directory_resource_id, 228 callback)); 229 } 230 231 CancelCallback GDataWapiService::Search( 232 const std::string& search_query, 233 const GetResourceListCallback& callback) { 234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 235 DCHECK(!search_query.empty()); 236 DCHECK(!callback.is_null()); 237 238 return sender_->StartRequestWithRetry( 239 new GetResourceListRequest(sender_.get(), 240 url_generator_, 241 GURL(), // No override url 242 0, // start changestamp 243 search_query, 244 std::string(), // no directory resource id 245 callback)); 246 } 247 248 CancelCallback GDataWapiService::SearchByTitle( 249 const std::string& title, 250 const std::string& directory_resource_id, 251 const GetResourceListCallback& callback) { 252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 253 DCHECK(!title.empty()); 254 DCHECK(!callback.is_null()); 255 256 return sender_->StartRequestWithRetry( 257 new SearchByTitleRequest(sender_.get(), 258 url_generator_, 259 title, 260 directory_resource_id, 261 callback)); 262 } 263 264 CancelCallback GDataWapiService::GetChangeList( 265 int64 start_changestamp, 266 const GetResourceListCallback& callback) { 267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 268 DCHECK(!callback.is_null()); 269 270 return sender_->StartRequestWithRetry( 271 new GetResourceListRequest(sender_.get(), 272 url_generator_, 273 GURL(), // No override url 274 start_changestamp, 275 std::string(), // empty search query 276 std::string(), // no directory resource id 277 callback)); 278 } 279 280 CancelCallback GDataWapiService::ContinueGetResourceList( 281 const GURL& override_url, 282 const GetResourceListCallback& callback) { 283 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 284 DCHECK(!override_url.is_empty()); 285 DCHECK(!callback.is_null()); 286 287 return sender_->StartRequestWithRetry( 288 new GetResourceListRequest(sender_.get(), 289 url_generator_, 290 override_url, 291 0, // start changestamp 292 std::string(), // empty search query 293 std::string(), // no directory resource id 294 callback)); 295 } 296 297 CancelCallback GDataWapiService::GetResourceEntry( 298 const std::string& resource_id, 299 const GetResourceEntryCallback& callback) { 300 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 301 DCHECK(!callback.is_null()); 302 303 return sender_->StartRequestWithRetry( 304 new GetResourceEntryRequest(sender_.get(), 305 url_generator_, 306 resource_id, 307 GURL(), 308 base::Bind(&ParseResourceEntryAndRun, 309 callback))); 310 } 311 312 CancelCallback GDataWapiService::GetShareUrl( 313 const std::string& resource_id, 314 const GURL& embed_origin, 315 const GetShareUrlCallback& callback) { 316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 317 DCHECK(!callback.is_null()); 318 319 return sender_->StartRequestWithRetry( 320 new GetResourceEntryRequest(sender_.get(), 321 url_generator_, 322 resource_id, 323 embed_origin, 324 base::Bind(&util::ParseShareUrlAndRun, 325 callback))); 326 } 327 328 CancelCallback GDataWapiService::GetAboutResource( 329 const GetAboutResourceCallback& callback) { 330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 331 DCHECK(!callback.is_null()); 332 333 return sender_->StartRequestWithRetry( 334 new GetAccountMetadataRequest( 335 sender_.get(), 336 url_generator_, 337 base::Bind(&ParseAboutResourceAndRun, callback), 338 false)); // Exclude installed apps. 339 } 340 341 CancelCallback GDataWapiService::GetAppList( 342 const GetAppListCallback& callback) { 343 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 344 DCHECK(!callback.is_null()); 345 346 return sender_->StartRequestWithRetry( 347 new GetAccountMetadataRequest(sender_.get(), 348 url_generator_, 349 base::Bind(&ParseAppListAndRun, callback), 350 true)); // Include installed apps. 351 } 352 353 CancelCallback GDataWapiService::DownloadFile( 354 const base::FilePath& local_cache_path, 355 const std::string& resource_id, 356 const DownloadActionCallback& download_action_callback, 357 const GetContentCallback& get_content_callback, 358 const ProgressCallback& progress_callback) { 359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 360 DCHECK(!download_action_callback.is_null()); 361 // get_content_callback and progress_callback may be null. 362 363 return sender_->StartRequestWithRetry( 364 new DownloadFileRequest(sender_.get(), 365 url_generator_, 366 download_action_callback, 367 get_content_callback, 368 progress_callback, 369 resource_id, 370 local_cache_path)); 371 } 372 373 CancelCallback GDataWapiService::DeleteResource( 374 const std::string& resource_id, 375 const std::string& etag, 376 const EntryActionCallback& callback) { 377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 378 DCHECK(!callback.is_null()); 379 380 return sender_->StartRequestWithRetry( 381 new DeleteResourceRequest(sender_.get(), 382 url_generator_, 383 callback, 384 resource_id, 385 etag)); 386 } 387 388 CancelCallback GDataWapiService::AddNewDirectory( 389 const std::string& parent_resource_id, 390 const std::string& directory_title, 391 const GetResourceEntryCallback& callback) { 392 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 393 DCHECK(!callback.is_null()); 394 395 return sender_->StartRequestWithRetry( 396 new CreateDirectoryRequest(sender_.get(), 397 url_generator_, 398 base::Bind(&ParseResourceEntryAndRun, 399 callback), 400 parent_resource_id, 401 directory_title)); 402 } 403 404 CancelCallback GDataWapiService::CopyResource( 405 const std::string& resource_id, 406 const std::string& parent_resource_id, 407 const std::string& new_title, 408 const GetResourceEntryCallback& callback) { 409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 410 DCHECK(!callback.is_null()); 411 412 // GData WAPI doesn't support "copy" of regular files. 413 // This method should never be called if GData WAPI is enabled. 414 // Instead, client code should download the file (if needed) and upload it. 415 NOTREACHED(); 416 return CancelCallback(); 417 } 418 419 CancelCallback GDataWapiService::CopyHostedDocument( 420 const std::string& resource_id, 421 const std::string& new_title, 422 const GetResourceEntryCallback& callback) { 423 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 424 DCHECK(!callback.is_null()); 425 426 return sender_->StartRequestWithRetry( 427 new CopyHostedDocumentRequest(sender_.get(), 428 url_generator_, 429 base::Bind(&ParseResourceEntryAndRun, 430 callback), 431 resource_id, 432 new_title)); 433 } 434 435 CancelCallback GDataWapiService::RenameResource( 436 const std::string& resource_id, 437 const std::string& new_title, 438 const EntryActionCallback& callback) { 439 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 440 DCHECK(!callback.is_null()); 441 442 return sender_->StartRequestWithRetry( 443 new RenameResourceRequest(sender_.get(), 444 url_generator_, 445 callback, 446 resource_id, 447 new_title)); 448 } 449 450 CancelCallback GDataWapiService::TouchResource( 451 const std::string& resource_id, 452 const base::Time& modified_date, 453 const base::Time& last_viewed_by_me_date, 454 const GetResourceEntryCallback& callback) { 455 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 456 DCHECK(!modified_date.is_null()); 457 DCHECK(!last_viewed_by_me_date.is_null()); 458 DCHECK(!callback.is_null()); 459 460 // Unfortunately, there is no way to support this method on GData WAPI. 461 // So, this should always return an error. 462 base::MessageLoop::current()->PostTask( 463 FROM_HERE, 464 base::Bind(callback, HTTP_NOT_IMPLEMENTED, 465 base::Passed(scoped_ptr<ResourceEntry>()))); 466 return base::Bind(&base::DoNothing); 467 } 468 469 CancelCallback GDataWapiService::AddResourceToDirectory( 470 const std::string& parent_resource_id, 471 const std::string& resource_id, 472 const EntryActionCallback& callback) { 473 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 474 DCHECK(!callback.is_null()); 475 476 return sender_->StartRequestWithRetry( 477 new AddResourceToDirectoryRequest(sender_.get(), 478 url_generator_, 479 callback, 480 parent_resource_id, 481 resource_id)); 482 } 483 484 CancelCallback GDataWapiService::RemoveResourceFromDirectory( 485 const std::string& parent_resource_id, 486 const std::string& resource_id, 487 const EntryActionCallback& callback) { 488 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 489 DCHECK(!callback.is_null()); 490 491 return sender_->StartRequestWithRetry( 492 new RemoveResourceFromDirectoryRequest(sender_.get(), 493 url_generator_, 494 callback, 495 parent_resource_id, 496 resource_id)); 497 } 498 499 CancelCallback GDataWapiService::InitiateUploadNewFile( 500 const std::string& content_type, 501 int64 content_length, 502 const std::string& parent_resource_id, 503 const std::string& title, 504 const InitiateUploadCallback& callback) { 505 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 506 DCHECK(!callback.is_null()); 507 DCHECK(!parent_resource_id.empty()); 508 509 return sender_->StartRequestWithRetry( 510 new InitiateUploadNewFileRequest(sender_.get(), 511 url_generator_, 512 callback, 513 content_type, 514 content_length, 515 parent_resource_id, 516 title)); 517 } 518 519 CancelCallback GDataWapiService::InitiateUploadExistingFile( 520 const std::string& content_type, 521 int64 content_length, 522 const std::string& resource_id, 523 const std::string& etag, 524 const InitiateUploadCallback& callback) { 525 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 526 DCHECK(!callback.is_null()); 527 DCHECK(!resource_id.empty()); 528 529 return sender_->StartRequestWithRetry( 530 new InitiateUploadExistingFileRequest(sender_.get(), 531 url_generator_, 532 callback, 533 content_type, 534 content_length, 535 resource_id, 536 etag)); 537 } 538 539 CancelCallback GDataWapiService::ResumeUpload( 540 const GURL& upload_url, 541 int64 start_position, 542 int64 end_position, 543 int64 content_length, 544 const std::string& content_type, 545 const base::FilePath& local_file_path, 546 const UploadRangeCallback& callback, 547 const ProgressCallback& progress_callback) { 548 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 549 DCHECK(!callback.is_null()); 550 551 return sender_->StartRequestWithRetry( 552 new ResumeUploadRequest(sender_.get(), 553 callback, 554 progress_callback, 555 upload_url, 556 start_position, 557 end_position, 558 content_length, 559 content_type, 560 local_file_path)); 561 } 562 563 CancelCallback GDataWapiService::GetUploadStatus( 564 const GURL& upload_url, 565 int64 content_length, 566 const UploadRangeCallback& callback) { 567 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 568 DCHECK(!callback.is_null()); 569 570 return sender_->StartRequestWithRetry( 571 new GetUploadStatusRequest(sender_.get(), 572 callback, 573 upload_url, 574 content_length)); 575 } 576 577 CancelCallback GDataWapiService::AuthorizeApp( 578 const std::string& resource_id, 579 const std::string& app_id, 580 const AuthorizeAppCallback& callback) { 581 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 582 DCHECK(!callback.is_null()); 583 584 return sender_->StartRequestWithRetry( 585 new AuthorizeAppRequest(sender_.get(), 586 url_generator_, 587 callback, 588 resource_id, 589 app_id)); 590 } 591 592 bool GDataWapiService::HasAccessToken() const { 593 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 594 595 return sender_->auth_service()->HasAccessToken(); 596 } 597 598 void GDataWapiService::RequestAccessToken(const AuthStatusCallback& callback) { 599 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 600 DCHECK(!callback.is_null()); 601 602 const std::string access_token = sender_->auth_service()->access_token(); 603 if (!access_token.empty()) { 604 callback.Run(google_apis::HTTP_NOT_MODIFIED, access_token); 605 return; 606 } 607 608 // Retrieve the new auth token. 609 sender_->auth_service()->StartAuthentication(callback); 610 } 611 612 bool GDataWapiService::HasRefreshToken() const { 613 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 614 return sender_->auth_service()->HasRefreshToken(); 615 } 616 617 void GDataWapiService::ClearAccessToken() { 618 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 619 sender_->auth_service()->ClearAccessToken(); 620 } 621 622 void GDataWapiService::ClearRefreshToken() { 623 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 624 sender_->auth_service()->ClearRefreshToken(); 625 } 626 627 void GDataWapiService::OnOAuth2RefreshTokenChanged() { 628 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 629 if (CanSendRequest()) { 630 FOR_EACH_OBSERVER( 631 DriveServiceObserver, observers_, OnReadyToSendRequests()); 632 } else if (!HasRefreshToken()) { 633 FOR_EACH_OBSERVER( 634 DriveServiceObserver, observers_, OnRefreshTokenInvalid()); 635 } 636 } 637 638 } // namespace drive 639