1 // Copyright 2013 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/sync_file_system/drive_backend_v1/api_util.h" 6 7 #include <algorithm> 8 #include <functional> 9 #include <sstream> 10 #include <string> 11 12 #include "base/file_util.h" 13 #include "base/sequenced_task_runner.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "base/threading/sequenced_worker_pool.h" 17 #include "base/values.h" 18 #include "chrome/browser/drive/drive_api_service.h" 19 #include "chrome/browser/drive/drive_api_util.h" 20 #include "chrome/browser/drive/drive_uploader.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 23 #include "chrome/browser/signin/signin_manager_factory.h" 24 #include "chrome/browser/sync_file_system/drive_backend/drive_backend_constants.h" 25 #include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h" 26 #include "chrome/browser/sync_file_system/logger.h" 27 #include "chrome/browser/sync_file_system/syncable_file_system_util.h" 28 #include "components/signin/core/browser/profile_oauth2_token_service.h" 29 #include "components/signin/core/browser/signin_manager.h" 30 #include "content/public/browser/browser_thread.h" 31 #include "extensions/common/constants.h" 32 #include "extensions/common/extension.h" 33 #include "google_apis/drive/drive_api_parser.h" 34 #include "google_apis/drive/drive_api_url_generator.h" 35 #include "google_apis/drive/gdata_wapi_url_generator.h" 36 37 namespace sync_file_system { 38 namespace drive_backend { 39 40 namespace { 41 42 enum ParentType { 43 PARENT_TYPE_ROOT_OR_EMPTY, 44 PARENT_TYPE_DIRECTORY, 45 }; 46 47 const char kFakeAccountId[] = "test_user (at) gmail.com"; 48 49 void EmptyGDataErrorCodeCallback(google_apis::GDataErrorCode error) {} 50 51 bool HasParentLinkTo(const ScopedVector<google_apis::Link>& links, 52 const std::string& parent_resource_id, 53 ParentType parent_type) { 54 bool has_parent = false; 55 56 for (ScopedVector<google_apis::Link>::const_iterator itr = links.begin(); 57 itr != links.end(); ++itr) { 58 if ((*itr)->type() == google_apis::Link::LINK_PARENT) { 59 has_parent = true; 60 if (drive::util::ExtractResourceIdFromUrl((*itr)->href()) == 61 parent_resource_id) 62 return true; 63 } 64 } 65 66 return parent_type == PARENT_TYPE_ROOT_OR_EMPTY && !has_parent; 67 } 68 69 struct TitleAndParentQuery 70 : std::unary_function<const google_apis::ResourceEntry*, bool> { 71 TitleAndParentQuery(const std::string& title, 72 const std::string& parent_resource_id, 73 ParentType parent_type) 74 : title(title), 75 parent_resource_id(parent_resource_id), 76 parent_type(parent_type) {} 77 78 bool operator()(const google_apis::ResourceEntry* entry) const { 79 return entry->title() == title && 80 HasParentLinkTo(entry->links(), parent_resource_id, parent_type); 81 } 82 83 const std::string& title; 84 const std::string& parent_resource_id; 85 ParentType parent_type; 86 }; 87 88 void FilterEntriesByTitleAndParent( 89 ScopedVector<google_apis::ResourceEntry>* entries, 90 const std::string& title, 91 const std::string& parent_resource_id, 92 ParentType parent_type) { 93 typedef ScopedVector<google_apis::ResourceEntry>::iterator iterator; 94 iterator itr = std::partition(entries->begin(), 95 entries->end(), 96 TitleAndParentQuery(title, 97 parent_resource_id, 98 parent_type)); 99 entries->erase(itr, entries->end()); 100 } 101 102 google_apis::ResourceEntry* GetDocumentByTitleAndParent( 103 const ScopedVector<google_apis::ResourceEntry>& entries, 104 const std::string& title, 105 const std::string& parent_resource_id, 106 ParentType parent_type) { 107 typedef ScopedVector<google_apis::ResourceEntry>::const_iterator iterator; 108 iterator found = 109 std::find_if(entries.begin(), 110 entries.end(), 111 TitleAndParentQuery(title, parent_resource_id, parent_type)); 112 if (found != entries.end()) 113 return *found; 114 return NULL; 115 } 116 117 void EntryAdapterForEnsureTitleUniqueness( 118 scoped_ptr<google_apis::ResourceEntry> entry, 119 const APIUtil::EnsureUniquenessCallback& callback, 120 APIUtil::EnsureUniquenessStatus status, 121 google_apis::GDataErrorCode error) { 122 callback.Run(error, status, entry.Pass()); 123 } 124 125 void UploadResultAdapter(const APIUtil::ResourceEntryCallback& callback, 126 google_apis::GDataErrorCode error, 127 const GURL& upload_location, 128 scoped_ptr<google_apis::FileResource> entry) { 129 callback.Run(error, entry ? 130 drive::util::ConvertFileResourceToResourceEntry(*entry) : 131 scoped_ptr<google_apis::ResourceEntry>()); 132 } 133 134 std::string GetMimeTypeFromTitle(const std::string& title) { 135 base::FilePath::StringType extension = 136 base::FilePath::FromUTF8Unsafe(title).Extension(); 137 std::string mime_type; 138 if (extension.empty() || 139 !net::GetWellKnownMimeTypeFromExtension(extension.substr(1), &mime_type)) 140 return kMimeTypeOctetStream; 141 return mime_type; 142 } 143 144 bool CreateTemporaryFile(const base::FilePath& dir_path, 145 webkit_blob::ScopedFile* temp_file) { 146 base::FilePath temp_file_path; 147 const bool success = base::CreateDirectory(dir_path) && 148 base::CreateTemporaryFileInDir(dir_path, &temp_file_path); 149 if (!success) 150 return success; 151 *temp_file = 152 webkit_blob::ScopedFile(temp_file_path, 153 webkit_blob::ScopedFile::DELETE_ON_SCOPE_OUT, 154 base::MessageLoopProxy::current().get()); 155 return success; 156 } 157 158 } // namespace 159 160 APIUtil::APIUtil(Profile* profile, 161 const base::FilePath& temp_dir_path) 162 : oauth_service_(ProfileOAuth2TokenServiceFactory::GetForProfile(profile)), 163 signin_manager_(SigninManagerFactory::GetForProfile(profile)), 164 upload_next_key_(0), 165 temp_dir_path_(temp_dir_path), 166 has_initialized_token_(false) { 167 base::SequencedWorkerPool* blocking_pool = 168 content::BrowserThread::GetBlockingPool(); 169 scoped_refptr<base::SequencedTaskRunner> task_runner( 170 blocking_pool->GetSequencedTaskRunner(blocking_pool->GetSequenceToken())); 171 DCHECK(!IsDriveAPIDisabled()); 172 drive_service_.reset(new drive::DriveAPIService( 173 oauth_service_, 174 profile->GetRequestContext(), 175 task_runner.get(), 176 GURL(google_apis::DriveApiUrlGenerator::kBaseUrlForProduction), 177 GURL(google_apis::DriveApiUrlGenerator::kBaseDownloadUrlForProduction), 178 GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction), 179 std::string() /* custom_user_agent */)); 180 drive_service_->Initialize(signin_manager_->GetAuthenticatedAccountId()); 181 drive_service_->AddObserver(this); 182 has_initialized_token_ = drive_service_->HasRefreshToken(); 183 184 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); 185 186 drive_uploader_.reset(new drive::DriveUploader( 187 drive_service_.get(), content::BrowserThread::GetBlockingPool())); 188 } 189 190 scoped_ptr<APIUtil> APIUtil::CreateForTesting( 191 const base::FilePath& temp_dir_path, 192 scoped_ptr<drive::DriveServiceInterface> drive_service, 193 scoped_ptr<drive::DriveUploaderInterface> drive_uploader) { 194 return make_scoped_ptr(new APIUtil( 195 temp_dir_path, 196 drive_service.Pass(), 197 drive_uploader.Pass(), 198 kFakeAccountId)); 199 } 200 201 APIUtil::APIUtil(const base::FilePath& temp_dir_path, 202 scoped_ptr<drive::DriveServiceInterface> drive_service, 203 scoped_ptr<drive::DriveUploaderInterface> drive_uploader, 204 const std::string& account_id) 205 : upload_next_key_(0), 206 temp_dir_path_(temp_dir_path) { 207 drive_service_ = drive_service.Pass(); 208 drive_service_->Initialize(account_id); 209 drive_service_->AddObserver(this); 210 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); 211 212 drive_uploader_ = drive_uploader.Pass(); 213 } 214 215 APIUtil::~APIUtil() { 216 DCHECK(CalledOnValidThread()); 217 net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); 218 drive_service_->RemoveObserver(this); 219 } 220 221 void APIUtil::AddObserver(APIUtilObserver* observer) { 222 DCHECK(CalledOnValidThread()); 223 observers_.AddObserver(observer); 224 } 225 226 void APIUtil::RemoveObserver(APIUtilObserver* observer) { 227 DCHECK(CalledOnValidThread()); 228 observers_.RemoveObserver(observer); 229 } 230 231 void APIUtil::GetDriveRootResourceId(const GDataErrorCallback& callback) { 232 DCHECK(CalledOnValidThread()); 233 DCHECK(!IsDriveAPIDisabled()); 234 DVLOG(2) << "Getting resource id for Drive root"; 235 236 drive_service_->GetAboutResource( 237 base::Bind(&APIUtil::DidGetDriveRootResourceId, AsWeakPtr(), callback)); 238 } 239 240 void APIUtil::DidGetDriveRootResourceId( 241 const GDataErrorCallback& callback, 242 google_apis::GDataErrorCode error, 243 scoped_ptr<google_apis::AboutResource> about_resource) { 244 DCHECK(CalledOnValidThread()); 245 246 if (error != google_apis::HTTP_SUCCESS) { 247 DVLOG(2) << "Error on getting resource id for Drive root: " << error; 248 callback.Run(error); 249 return; 250 } 251 252 DCHECK(about_resource); 253 root_resource_id_ = about_resource->root_folder_id(); 254 DCHECK(!root_resource_id_.empty()); 255 DVLOG(2) << "Got resource id for Drive root: " << root_resource_id_; 256 callback.Run(error); 257 } 258 259 void APIUtil::GetDriveDirectoryForSyncRoot(const ResourceIdCallback& callback) { 260 DCHECK(CalledOnValidThread()); 261 262 if (GetRootResourceId().empty()) { 263 GetDriveRootResourceId( 264 base::Bind(&APIUtil::DidGetDriveRootResourceIdForGetSyncRoot, 265 AsWeakPtr(), callback)); 266 return; 267 } 268 269 DVLOG(2) << "Getting Drive directory for SyncRoot"; 270 std::string directory_name(GetSyncRootDirectoryName()); 271 SearchByTitle(directory_name, 272 std::string(), 273 base::Bind(&APIUtil::DidGetDirectory, 274 AsWeakPtr(), 275 std::string(), 276 directory_name, 277 callback)); 278 } 279 280 void APIUtil::DidGetDriveRootResourceIdForGetSyncRoot( 281 const ResourceIdCallback& callback, 282 google_apis::GDataErrorCode error) { 283 DCHECK(CalledOnValidThread()); 284 if (error != google_apis::HTTP_SUCCESS) { 285 DVLOG(2) << "Error on getting Drive directory for SyncRoot: " << error; 286 callback.Run(error, std::string()); 287 return; 288 } 289 GetDriveDirectoryForSyncRoot(callback); 290 } 291 292 void APIUtil::GetDriveDirectoryForOrigin( 293 const std::string& sync_root_resource_id, 294 const GURL& origin, 295 const ResourceIdCallback& callback) { 296 DCHECK(CalledOnValidThread()); 297 DVLOG(2) << "Getting Drive directory for Origin: " << origin; 298 299 std::string directory_name(OriginToDirectoryTitle(origin)); 300 SearchByTitle(directory_name, 301 sync_root_resource_id, 302 base::Bind(&APIUtil::DidGetDirectory, 303 AsWeakPtr(), 304 sync_root_resource_id, 305 directory_name, 306 callback)); 307 } 308 309 void APIUtil::DidGetDirectory(const std::string& parent_resource_id, 310 const std::string& directory_name, 311 const ResourceIdCallback& callback, 312 google_apis::GDataErrorCode error, 313 scoped_ptr<google_apis::ResourceList> feed) { 314 DCHECK(CalledOnValidThread()); 315 DCHECK(base::IsStringASCII(directory_name)); 316 317 if (error != google_apis::HTTP_SUCCESS) { 318 DVLOG(2) << "Error on getting Drive directory: " << error; 319 callback.Run(error, std::string()); 320 return; 321 } 322 323 std::string resource_id; 324 ParentType parent_type = PARENT_TYPE_DIRECTORY; 325 if (parent_resource_id.empty()) { 326 resource_id = GetRootResourceId(); 327 DCHECK(!resource_id.empty()); 328 parent_type = PARENT_TYPE_ROOT_OR_EMPTY; 329 } else { 330 resource_id = parent_resource_id; 331 } 332 std::string title(directory_name); 333 google_apis::ResourceEntry* entry = GetDocumentByTitleAndParent( 334 feed->entries(), title, resource_id, parent_type); 335 if (!entry) { 336 DVLOG(2) << "Directory not found. Creating: " << directory_name; 337 drive_service_->AddNewDirectory( 338 resource_id, 339 directory_name, 340 drive::DriveServiceInterface::AddNewDirectoryOptions(), 341 base::Bind(&APIUtil::DidCreateDirectory, 342 AsWeakPtr(), 343 parent_resource_id, 344 title, 345 callback)); 346 return; 347 } 348 DVLOG(2) << "Found Drive directory."; 349 350 // TODO(tzik): Handle error. 351 DCHECK_EQ(google_apis::ENTRY_KIND_FOLDER, entry->kind()); 352 DCHECK_EQ(directory_name, entry->title()); 353 354 if (entry->title() == GetSyncRootDirectoryName()) 355 EnsureSyncRootIsNotInMyDrive(entry->resource_id()); 356 357 callback.Run(error, entry->resource_id()); 358 } 359 360 void APIUtil::DidCreateDirectory(const std::string& parent_resource_id, 361 const std::string& title, 362 const ResourceIdCallback& callback, 363 google_apis::GDataErrorCode error, 364 scoped_ptr<google_apis::FileResource> entry) { 365 DCHECK(CalledOnValidThread()); 366 367 if (error != google_apis::HTTP_SUCCESS && 368 error != google_apis::HTTP_CREATED) { 369 DVLOG(2) << "Error on creating Drive directory: " << error; 370 callback.Run(error, std::string()); 371 return; 372 } 373 DVLOG(2) << "Created Drive directory."; 374 375 DCHECK(entry); 376 // Check if any other client creates a directory with same title. 377 EnsureTitleUniqueness( 378 parent_resource_id, 379 title, 380 base::Bind(&APIUtil::DidEnsureUniquenessForCreateDirectory, 381 AsWeakPtr(), 382 callback)); 383 } 384 385 void APIUtil::DidEnsureUniquenessForCreateDirectory( 386 const ResourceIdCallback& callback, 387 google_apis::GDataErrorCode error, 388 EnsureUniquenessStatus status, 389 scoped_ptr<google_apis::ResourceEntry> entry) { 390 DCHECK(CalledOnValidThread()); 391 392 if (error != google_apis::HTTP_SUCCESS) { 393 callback.Run(error, std::string()); 394 return; 395 } 396 397 if (status == NO_DUPLICATES_FOUND) 398 error = google_apis::HTTP_CREATED; 399 400 DCHECK(entry) << "No entry: " << error; 401 402 if (!entry->is_folder()) { 403 // TODO(kinuko): Fix this. http://crbug.com/237090 404 util::Log( 405 logging::LOG_ERROR, 406 FROM_HERE, 407 "A file is left for CreateDirectory due to file-folder conflict!"); 408 callback.Run(google_apis::HTTP_CONFLICT, std::string()); 409 return; 410 } 411 412 if (entry->title() == GetSyncRootDirectoryName()) 413 EnsureSyncRootIsNotInMyDrive(entry->resource_id()); 414 415 callback.Run(error, entry->resource_id()); 416 } 417 418 void APIUtil::GetLargestChangeStamp(const ChangeStampCallback& callback) { 419 DCHECK(CalledOnValidThread()); 420 DVLOG(2) << "Getting largest change id"; 421 422 drive_service_->GetAboutResource( 423 base::Bind(&APIUtil::DidGetLargestChangeStamp, AsWeakPtr(), callback)); 424 } 425 426 void APIUtil::GetResourceEntry(const std::string& resource_id, 427 const ResourceEntryCallback& callback) { 428 DCHECK(CalledOnValidThread()); 429 DVLOG(2) << "Getting ResourceEntry for: " << resource_id; 430 431 drive_service_->GetFileResource( 432 resource_id, 433 base::Bind(&APIUtil::DidGetFileResource, AsWeakPtr(), callback)); 434 } 435 436 void APIUtil::DidGetLargestChangeStamp( 437 const ChangeStampCallback& callback, 438 google_apis::GDataErrorCode error, 439 scoped_ptr<google_apis::AboutResource> about_resource) { 440 DCHECK(CalledOnValidThread()); 441 442 int64 largest_change_id = 0; 443 if (error == google_apis::HTTP_SUCCESS) { 444 DCHECK(about_resource); 445 largest_change_id = about_resource->largest_change_id(); 446 root_resource_id_ = about_resource->root_folder_id(); 447 DVLOG(2) << "Got largest change id: " << largest_change_id; 448 } else { 449 DVLOG(2) << "Error on getting largest change id: " << error; 450 } 451 452 callback.Run(error, largest_change_id); 453 } 454 455 void APIUtil::SearchByTitle(const std::string& title, 456 const std::string& directory_resource_id, 457 const ResourceListCallback& callback) { 458 DCHECK(CalledOnValidThread()); 459 DCHECK(!title.empty()); 460 DVLOG(2) << "Searching resources in the directory [" << directory_resource_id 461 << "] with title [" << title << "]"; 462 463 drive_service_->SearchByTitle( 464 title, 465 directory_resource_id, 466 base::Bind(&APIUtil::DidGetFileList, AsWeakPtr(), callback)); 467 } 468 469 void APIUtil::ListFiles(const std::string& directory_resource_id, 470 const ResourceListCallback& callback) { 471 DCHECK(CalledOnValidThread()); 472 DVLOG(2) << "Listing resources in the directory [" << directory_resource_id 473 << "]"; 474 475 drive_service_->GetFileListInDirectory( 476 directory_resource_id, 477 base::Bind(&APIUtil::DidGetFileList, AsWeakPtr(), callback)); 478 } 479 480 void APIUtil::ListChanges(int64 start_changestamp, 481 const ResourceListCallback& callback) { 482 DCHECK(CalledOnValidThread()); 483 DVLOG(2) << "Listing changes since: " << start_changestamp; 484 485 drive_service_->GetChangeList( 486 start_changestamp, 487 base::Bind(&APIUtil::DidGetChangeList, AsWeakPtr(), callback)); 488 } 489 490 void APIUtil::ContinueListing(const GURL& next_link, 491 const ResourceListCallback& callback) { 492 DCHECK(CalledOnValidThread()); 493 DVLOG(2) << "Continue listing on feed: " << next_link.spec(); 494 495 drive_service_->GetRemainingFileList( 496 next_link, 497 base::Bind(&APIUtil::DidGetFileList, AsWeakPtr(), callback)); 498 } 499 500 void APIUtil::DownloadFile(const std::string& resource_id, 501 const std::string& local_file_md5, 502 const DownloadFileCallback& callback) { 503 DCHECK(CalledOnValidThread()); 504 DCHECK(!temp_dir_path_.empty()); 505 DVLOG(2) << "Downloading file [" << resource_id << "]"; 506 507 scoped_ptr<webkit_blob::ScopedFile> temp_file(new webkit_blob::ScopedFile); 508 webkit_blob::ScopedFile* temp_file_ptr = temp_file.get(); 509 content::BrowserThread::PostTaskAndReplyWithResult( 510 content::BrowserThread::FILE, FROM_HERE, 511 base::Bind(&CreateTemporaryFile, temp_dir_path_, temp_file_ptr), 512 base::Bind(&APIUtil::DidGetTemporaryFileForDownload, 513 AsWeakPtr(), resource_id, local_file_md5, 514 base::Passed(&temp_file), callback)); 515 } 516 517 void APIUtil::UploadNewFile(const std::string& directory_resource_id, 518 const base::FilePath& local_file_path, 519 const std::string& title, 520 const UploadFileCallback& callback) { 521 DCHECK(CalledOnValidThread()); 522 DVLOG(2) << "Uploading new file into the directory [" << directory_resource_id 523 << "] with title [" << title << "]"; 524 525 std::string mime_type = GetMimeTypeFromTitle(title); 526 UploadKey upload_key = RegisterUploadCallback(callback); 527 ResourceEntryCallback did_upload_callback = 528 base::Bind(&APIUtil::DidUploadNewFile, 529 AsWeakPtr(), 530 directory_resource_id, 531 title, 532 upload_key); 533 drive_uploader_->UploadNewFile( 534 directory_resource_id, 535 local_file_path, 536 title, 537 mime_type, 538 drive::DriveUploader::UploadNewFileOptions(), 539 base::Bind(&UploadResultAdapter, did_upload_callback), 540 google_apis::ProgressCallback()); 541 } 542 543 void APIUtil::UploadExistingFile(const std::string& resource_id, 544 const std::string& remote_file_md5, 545 const base::FilePath& local_file_path, 546 const UploadFileCallback& callback) { 547 DCHECK(CalledOnValidThread()); 548 DVLOG(2) << "Uploading existing file [" << resource_id << "]"; 549 drive_service_->GetFileResource( 550 resource_id, 551 base::Bind(&APIUtil::DidGetFileResource, 552 AsWeakPtr(), 553 base::Bind(&APIUtil::UploadExistingFileInternal, 554 AsWeakPtr(), 555 remote_file_md5, 556 local_file_path, 557 callback))); 558 } 559 560 void APIUtil::CreateDirectory(const std::string& parent_resource_id, 561 const std::string& title, 562 const ResourceIdCallback& callback) { 563 DCHECK(CalledOnValidThread()); 564 // TODO(kinuko): This will call EnsureTitleUniqueness and will delete 565 // directories if there're duplicated directories. This must be ok 566 // for current design but we'll need to merge directories when we support 567 // 'real' directories. 568 drive_service_->AddNewDirectory( 569 parent_resource_id, 570 title, 571 drive::DriveServiceInterface::AddNewDirectoryOptions(), 572 base::Bind(&APIUtil::DidCreateDirectory, 573 AsWeakPtr(), 574 parent_resource_id, 575 title, 576 callback)); 577 } 578 579 void APIUtil::DeleteFile(const std::string& resource_id, 580 const std::string& remote_file_md5, 581 const GDataErrorCallback& callback) { 582 DCHECK(CalledOnValidThread()); 583 DVLOG(2) << "Deleting file: " << resource_id; 584 585 // Load actual remote_file_md5 to check for conflict before deletion. 586 if (!remote_file_md5.empty()) { 587 drive_service_->GetFileResource( 588 resource_id, 589 base::Bind(&APIUtil::DidGetFileResource, 590 AsWeakPtr(), 591 base::Bind(&APIUtil::DeleteFileInternal, 592 AsWeakPtr(), 593 remote_file_md5, 594 callback))); 595 return; 596 } 597 598 // Expected remote_file_md5 is empty so do a force delete. 599 drive_service_->TrashResource( 600 resource_id, 601 base::Bind(&APIUtil::DidDeleteFile, AsWeakPtr(), callback)); 602 return; 603 } 604 605 void APIUtil::EnsureSyncRootIsNotInMyDrive( 606 const std::string& sync_root_resource_id) { 607 DCHECK(CalledOnValidThread()); 608 609 if (GetRootResourceId().empty()) { 610 GetDriveRootResourceId( 611 base::Bind(&APIUtil::DidGetDriveRootResourceIdForEnsureSyncRoot, 612 AsWeakPtr(), sync_root_resource_id)); 613 return; 614 } 615 616 DVLOG(2) << "Ensuring the sync root directory is not in 'My Drive'."; 617 drive_service_->RemoveResourceFromDirectory( 618 GetRootResourceId(), 619 sync_root_resource_id, 620 base::Bind(&EmptyGDataErrorCodeCallback)); 621 } 622 623 void APIUtil::DidGetDriveRootResourceIdForEnsureSyncRoot( 624 const std::string& sync_root_resource_id, 625 google_apis::GDataErrorCode error) { 626 DCHECK(CalledOnValidThread()); 627 628 if (error != google_apis::HTTP_SUCCESS) { 629 DVLOG(2) << "Error on ensuring the sync root directory is not in" 630 << " 'My Drive': " << error; 631 // Give up ensuring the sync root directory is not in 'My Drive'. This will 632 // be retried at some point. 633 return; 634 } 635 636 DCHECK(!GetRootResourceId().empty()); 637 EnsureSyncRootIsNotInMyDrive(sync_root_resource_id); 638 } 639 640 // static 641 // TODO(calvinlo): Delete this when Sync Directory Operations are supported by 642 // default. 643 std::string APIUtil::GetSyncRootDirectoryName() { 644 return IsSyncFSDirectoryOperationEnabled() ? kSyncRootFolderTitleDev 645 : kSyncRootFolderTitle; 646 } 647 648 // static 649 std::string APIUtil::OriginToDirectoryTitle(const GURL& origin) { 650 DCHECK(origin.SchemeIs(extensions::kExtensionScheme)); 651 return origin.host(); 652 } 653 654 // static 655 GURL APIUtil::DirectoryTitleToOrigin(const std::string& title) { 656 return extensions::Extension::GetBaseURLFromExtensionId(title); 657 } 658 659 void APIUtil::OnReadyToSendRequests() { 660 DCHECK(CalledOnValidThread()); 661 if (!has_initialized_token_) { 662 drive_service_->Initialize(signin_manager_->GetAuthenticatedAccountId()); 663 has_initialized_token_ = true; 664 } 665 FOR_EACH_OBSERVER(APIUtilObserver, observers_, OnAuthenticated()); 666 } 667 668 void APIUtil::OnConnectionTypeChanged( 669 net::NetworkChangeNotifier::ConnectionType type) { 670 DCHECK(CalledOnValidThread()); 671 if (type != net::NetworkChangeNotifier::CONNECTION_NONE) { 672 FOR_EACH_OBSERVER(APIUtilObserver, observers_, OnNetworkConnected()); 673 return; 674 } 675 // We're now disconnected, reset the drive_uploader_ to force stop 676 // uploading, otherwise the uploader may get stuck. 677 // TODO(kinuko): Check the uploader behavior if it's the expected behavior 678 // (http://crbug.com/223818) 679 CancelAllUploads(google_apis::GDATA_NO_CONNECTION); 680 } 681 682 void APIUtil::DidGetFileList(const ResourceListCallback& callback, 683 google_apis::GDataErrorCode error, 684 scoped_ptr<google_apis::FileList> file_list) { 685 DCHECK(CalledOnValidThread()); 686 687 if (error != google_apis::HTTP_SUCCESS) { 688 DVLOG(2) << "Error on listing files: " << error; 689 callback.Run(error, scoped_ptr<google_apis::ResourceList>()); 690 return; 691 } 692 693 DVLOG(2) << "Got file list"; 694 DCHECK(file_list); 695 callback.Run(error, drive::util::ConvertFileListToResourceList(*file_list)); 696 } 697 698 void APIUtil::DidGetChangeList( 699 const ResourceListCallback& callback, 700 google_apis::GDataErrorCode error, 701 scoped_ptr<google_apis::ChangeList> change_list) { 702 DCHECK(CalledOnValidThread()); 703 704 if (error != google_apis::HTTP_SUCCESS) { 705 DVLOG(2) << "Error on listing changes: " << error; 706 callback.Run(error, scoped_ptr<google_apis::ResourceList>()); 707 return; 708 } 709 710 DVLOG(2) << "Got change list"; 711 DCHECK(change_list); 712 callback.Run(error, 713 drive::util::ConvertChangeListToResourceList(*change_list)); 714 } 715 716 void APIUtil::DidGetFileResource( 717 const ResourceEntryCallback& callback, 718 google_apis::GDataErrorCode error, 719 scoped_ptr<google_apis::FileResource> entry) { 720 DCHECK(CalledOnValidThread()); 721 722 if (error != google_apis::HTTP_SUCCESS) { 723 DVLOG(2) << "Error on getting resource entry:" << error; 724 callback.Run(error, scoped_ptr<google_apis::ResourceEntry>()); 725 return; 726 } 727 DCHECK(entry); 728 729 if (entry->labels().is_trashed()) { 730 DVLOG(2) << "Got resource entry, the entry was trashed."; 731 callback.Run(google_apis::HTTP_NOT_FOUND, 732 drive::util::ConvertFileResourceToResourceEntry(*entry)); 733 return; 734 } 735 736 DVLOG(2) << "Got resource entry"; 737 callback.Run(error, drive::util::ConvertFileResourceToResourceEntry(*entry)); 738 } 739 740 void APIUtil::DidGetTemporaryFileForDownload( 741 const std::string& resource_id, 742 const std::string& local_file_md5, 743 scoped_ptr<webkit_blob::ScopedFile> local_file, 744 const DownloadFileCallback& callback, 745 bool success) { 746 if (!success) { 747 DVLOG(2) << "Error in creating a temp file under " 748 << temp_dir_path_.value(); 749 callback.Run(google_apis::GDATA_FILE_ERROR, std::string(), 0, base::Time(), 750 local_file->Pass()); 751 return; 752 } 753 drive_service_->GetFileResource( 754 resource_id, 755 base::Bind(&APIUtil::DidGetFileResource, 756 AsWeakPtr(), 757 base::Bind(&APIUtil::DownloadFileInternal, 758 AsWeakPtr(), 759 local_file_md5, 760 base::Passed(&local_file), 761 callback))); 762 } 763 764 void APIUtil::DownloadFileInternal( 765 const std::string& local_file_md5, 766 scoped_ptr<webkit_blob::ScopedFile> local_file, 767 const DownloadFileCallback& callback, 768 google_apis::GDataErrorCode error, 769 scoped_ptr<google_apis::ResourceEntry> entry) { 770 DCHECK(CalledOnValidThread()); 771 772 if (error != google_apis::HTTP_SUCCESS) { 773 DVLOG(2) << "Error on getting resource entry for download"; 774 callback.Run(error, std::string(), 0, base::Time(), local_file->Pass()); 775 return; 776 } 777 DCHECK(entry); 778 779 DVLOG(2) << "Got resource entry for download"; 780 // If local file and remote file are same, cancel the download. 781 if (local_file_md5 == entry->file_md5()) { 782 callback.Run(google_apis::HTTP_NOT_MODIFIED, 783 local_file_md5, 784 entry->file_size(), 785 entry->updated_time(), 786 local_file->Pass()); 787 return; 788 } 789 790 DVLOG(2) << "Downloading file: " << entry->resource_id(); 791 const std::string& resource_id = entry->resource_id(); 792 const base::FilePath& local_file_path = local_file->path(); 793 drive_service_->DownloadFile(local_file_path, 794 resource_id, 795 base::Bind(&APIUtil::DidDownloadFile, 796 AsWeakPtr(), 797 base::Passed(&entry), 798 base::Passed(&local_file), 799 callback), 800 google_apis::GetContentCallback(), 801 google_apis::ProgressCallback()); 802 } 803 804 void APIUtil::DidDownloadFile(scoped_ptr<google_apis::ResourceEntry> entry, 805 scoped_ptr<webkit_blob::ScopedFile> local_file, 806 const DownloadFileCallback& callback, 807 google_apis::GDataErrorCode error, 808 const base::FilePath& downloaded_file_path) { 809 DCHECK(CalledOnValidThread()); 810 if (error == google_apis::HTTP_SUCCESS) 811 DVLOG(2) << "Download completed"; 812 else 813 DVLOG(2) << "Error on downloading file: " << error; 814 815 callback.Run( 816 error, entry->file_md5(), entry->file_size(), entry->updated_time(), 817 local_file->Pass()); 818 } 819 820 void APIUtil::DidUploadNewFile(const std::string& parent_resource_id, 821 const std::string& title, 822 UploadKey upload_key, 823 google_apis::GDataErrorCode error, 824 scoped_ptr<google_apis::ResourceEntry> entry) { 825 UploadFileCallback callback = GetAndUnregisterUploadCallback(upload_key); 826 DCHECK(!callback.is_null()); 827 if (error != google_apis::HTTP_SUCCESS && 828 error != google_apis::HTTP_CREATED) { 829 DVLOG(2) << "Error on uploading new file: " << error; 830 callback.Run(error, std::string(), std::string()); 831 return; 832 } 833 834 DVLOG(2) << "Upload completed"; 835 EnsureTitleUniqueness(parent_resource_id, 836 title, 837 base::Bind(&APIUtil::DidEnsureUniquenessForCreateFile, 838 AsWeakPtr(), 839 entry->resource_id(), 840 callback)); 841 } 842 843 void APIUtil::DidEnsureUniquenessForCreateFile( 844 const std::string& expected_resource_id, 845 const UploadFileCallback& callback, 846 google_apis::GDataErrorCode error, 847 EnsureUniquenessStatus status, 848 scoped_ptr<google_apis::ResourceEntry> entry) { 849 if (error != google_apis::HTTP_SUCCESS) { 850 DVLOG(2) << "Error on uploading new file: " << error; 851 callback.Run(error, std::string(), std::string()); 852 return; 853 } 854 855 switch (status) { 856 case NO_DUPLICATES_FOUND: 857 // The file was uploaded successfully and no conflict was detected. 858 DCHECK(entry); 859 DVLOG(2) << "No conflict detected on uploading new file"; 860 callback.Run( 861 google_apis::HTTP_CREATED, entry->resource_id(), entry->file_md5()); 862 return; 863 864 case RESOLVED_DUPLICATES: 865 // The file was uploaded successfully but a conflict was detected. 866 // The duplicated file was deleted successfully. 867 DCHECK(entry); 868 if (entry->resource_id() != expected_resource_id) { 869 // TODO(kinuko): We should check local vs remote md5 here. 870 DVLOG(2) << "Conflict detected on uploading new file"; 871 callback.Run(google_apis::HTTP_CONFLICT, 872 entry->resource_id(), 873 entry->file_md5()); 874 return; 875 } 876 877 DVLOG(2) << "Conflict detected on uploading new file and resolved"; 878 callback.Run( 879 google_apis::HTTP_CREATED, entry->resource_id(), entry->file_md5()); 880 return; 881 882 default: 883 NOTREACHED() << "Unknown status from EnsureTitleUniqueness:" << status 884 << " for " << expected_resource_id; 885 } 886 } 887 888 void APIUtil::UploadExistingFileInternal( 889 const std::string& remote_file_md5, 890 const base::FilePath& local_file_path, 891 const UploadFileCallback& callback, 892 google_apis::GDataErrorCode error, 893 scoped_ptr<google_apis::ResourceEntry> entry) { 894 DCHECK(CalledOnValidThread()); 895 896 if (error != google_apis::HTTP_SUCCESS) { 897 DVLOG(2) << "Error on uploading existing file: " << error; 898 callback.Run(error, std::string(), std::string()); 899 return; 900 } 901 DCHECK(entry); 902 903 // If remote file's hash value is different from the expected one, conflict 904 // might have occurred. 905 if (!remote_file_md5.empty() && remote_file_md5 != entry->file_md5()) { 906 DVLOG(2) << "Conflict detected before uploading existing file"; 907 callback.Run(google_apis::HTTP_CONFLICT, std::string(), std::string()); 908 return; 909 } 910 911 drive::DriveUploader::UploadExistingFileOptions options; 912 options.etag = entry->etag(); 913 std::string mime_type = GetMimeTypeFromTitle(entry->title()); 914 UploadKey upload_key = RegisterUploadCallback(callback); 915 ResourceEntryCallback did_upload_callback = 916 base::Bind(&APIUtil::DidUploadExistingFile, AsWeakPtr(), upload_key); 917 drive_uploader_->UploadExistingFile( 918 entry->resource_id(), 919 local_file_path, 920 mime_type, 921 options, 922 base::Bind(&UploadResultAdapter, did_upload_callback), 923 google_apis::ProgressCallback()); 924 } 925 926 bool APIUtil::IsAuthenticated() const { 927 return drive_service_->HasRefreshToken(); 928 } 929 930 void APIUtil::DidUploadExistingFile( 931 UploadKey upload_key, 932 google_apis::GDataErrorCode error, 933 scoped_ptr<google_apis::ResourceEntry> entry) { 934 DCHECK(CalledOnValidThread()); 935 UploadFileCallback callback = GetAndUnregisterUploadCallback(upload_key); 936 DCHECK(!callback.is_null()); 937 if (error != google_apis::HTTP_SUCCESS) { 938 DVLOG(2) << "Error on uploading existing file: " << error; 939 callback.Run(error, std::string(), std::string()); 940 return; 941 } 942 943 DCHECK(entry); 944 DVLOG(2) << "Upload completed"; 945 callback.Run(error, entry->resource_id(), entry->file_md5()); 946 } 947 948 void APIUtil::DeleteFileInternal(const std::string& remote_file_md5, 949 const GDataErrorCallback& callback, 950 google_apis::GDataErrorCode error, 951 scoped_ptr<google_apis::ResourceEntry> entry) { 952 DCHECK(CalledOnValidThread()); 953 954 if (error != google_apis::HTTP_SUCCESS) { 955 DVLOG(2) << "Error on getting resource entry for deleting file: " << error; 956 callback.Run(error); 957 return; 958 } 959 DCHECK(entry); 960 961 // If remote file's hash value is different from the expected one, conflict 962 // might have occurred. 963 if (!remote_file_md5.empty() && remote_file_md5 != entry->file_md5()) { 964 DVLOG(2) << "Conflict detected before deleting file"; 965 callback.Run(google_apis::HTTP_CONFLICT); 966 return; 967 } 968 DVLOG(2) << "Got resource entry for deleting file"; 969 970 // Move the file to trash (don't delete it completely). 971 drive_service_->TrashResource( 972 entry->resource_id(), 973 base::Bind(&APIUtil::DidDeleteFile, AsWeakPtr(), callback)); 974 } 975 976 void APIUtil::DidDeleteFile(const GDataErrorCallback& callback, 977 google_apis::GDataErrorCode error) { 978 DCHECK(CalledOnValidThread()); 979 if (error == google_apis::HTTP_SUCCESS) 980 DVLOG(2) << "Deletion completed"; 981 else 982 DVLOG(2) << "Error on deleting file: " << error; 983 984 callback.Run(error); 985 } 986 987 void APIUtil::EnsureTitleUniqueness(const std::string& parent_resource_id, 988 const std::string& expected_title, 989 const EnsureUniquenessCallback& callback) { 990 DCHECK(CalledOnValidThread()); 991 DVLOG(2) << "Checking if there's no conflict on entry creation"; 992 993 const google_apis::GetResourceListCallback& bound_callback = 994 base::Bind(&APIUtil::DidListEntriesToEnsureUniqueness, 995 AsWeakPtr(), 996 parent_resource_id, 997 expected_title, 998 callback); 999 1000 SearchByTitle(expected_title, parent_resource_id, bound_callback); 1001 } 1002 1003 void APIUtil::DidListEntriesToEnsureUniqueness( 1004 const std::string& parent_resource_id, 1005 const std::string& expected_title, 1006 const EnsureUniquenessCallback& callback, 1007 google_apis::GDataErrorCode error, 1008 scoped_ptr<google_apis::ResourceList> feed) { 1009 DCHECK(CalledOnValidThread()); 1010 1011 if (error != google_apis::HTTP_SUCCESS) { 1012 DVLOG(2) << "Error on listing resource for ensuring title uniqueness"; 1013 callback.Run( 1014 error, NO_DUPLICATES_FOUND, scoped_ptr<google_apis::ResourceEntry>()); 1015 return; 1016 } 1017 DVLOG(2) << "Got resource list for ensuring title uniqueness"; 1018 1019 // This filtering is needed only on WAPI. Once we move to Drive API we can 1020 // drop this. 1021 std::string resource_id; 1022 ParentType parent_type = PARENT_TYPE_DIRECTORY; 1023 if (parent_resource_id.empty()) { 1024 resource_id = GetRootResourceId(); 1025 DCHECK(!resource_id.empty()); 1026 parent_type = PARENT_TYPE_ROOT_OR_EMPTY; 1027 } else { 1028 resource_id = parent_resource_id; 1029 } 1030 ScopedVector<google_apis::ResourceEntry> entries; 1031 entries.swap(*feed->mutable_entries()); 1032 FilterEntriesByTitleAndParent( 1033 &entries, expected_title, resource_id, parent_type); 1034 1035 if (entries.empty()) { 1036 DVLOG(2) << "Uploaded file is not found"; 1037 callback.Run(google_apis::HTTP_NOT_FOUND, 1038 NO_DUPLICATES_FOUND, 1039 scoped_ptr<google_apis::ResourceEntry>()); 1040 return; 1041 } 1042 1043 if (entries.size() >= 2) { 1044 DVLOG(2) << "Conflict detected on creating entry"; 1045 for (size_t i = 0; i < entries.size() - 1; ++i) { 1046 // TODO(tzik): Replace published_time with creation time after we move to 1047 // Drive API. 1048 if (entries[i]->published_time() < entries.back()->published_time()) 1049 std::swap(entries[i], entries.back()); 1050 } 1051 1052 scoped_ptr<google_apis::ResourceEntry> earliest_entry(entries.back()); 1053 entries.back() = NULL; 1054 entries.get().pop_back(); 1055 1056 DeleteEntriesForEnsuringTitleUniqueness( 1057 entries.Pass(), 1058 base::Bind(&EntryAdapterForEnsureTitleUniqueness, 1059 base::Passed(&earliest_entry), 1060 callback, 1061 RESOLVED_DUPLICATES)); 1062 return; 1063 } 1064 1065 DVLOG(2) << "no conflict detected"; 1066 DCHECK_EQ(1u, entries.size()); 1067 scoped_ptr<google_apis::ResourceEntry> entry(entries.front()); 1068 entries.weak_clear(); 1069 1070 callback.Run(google_apis::HTTP_SUCCESS, NO_DUPLICATES_FOUND, entry.Pass()); 1071 } 1072 1073 void APIUtil::DeleteEntriesForEnsuringTitleUniqueness( 1074 ScopedVector<google_apis::ResourceEntry> entries, 1075 const GDataErrorCallback& callback) { 1076 DCHECK(CalledOnValidThread()); 1077 DVLOG(2) << "Cleaning up conflict on entry creation"; 1078 1079 if (entries.empty()) { 1080 callback.Run(google_apis::HTTP_SUCCESS); 1081 return; 1082 } 1083 1084 scoped_ptr<google_apis::ResourceEntry> entry(entries.back()); 1085 entries.back() = NULL; 1086 entries.get().pop_back(); 1087 1088 // We don't care conflicts here as other clients may be also deleting this 1089 // file, so passing an empty etag. 1090 drive_service_->TrashResource( 1091 entry->resource_id(), 1092 base::Bind(&APIUtil::DidDeleteEntriesForEnsuringTitleUniqueness, 1093 AsWeakPtr(), 1094 base::Passed(&entries), 1095 callback)); 1096 } 1097 1098 void APIUtil::DidDeleteEntriesForEnsuringTitleUniqueness( 1099 ScopedVector<google_apis::ResourceEntry> entries, 1100 const GDataErrorCallback& callback, 1101 google_apis::GDataErrorCode error) { 1102 DCHECK(CalledOnValidThread()); 1103 1104 if (error != google_apis::HTTP_SUCCESS && 1105 error != google_apis::HTTP_NOT_FOUND) { 1106 DVLOG(2) << "Error on deleting file: " << error; 1107 callback.Run(error); 1108 return; 1109 } 1110 1111 DVLOG(2) << "Deletion completed"; 1112 DeleteEntriesForEnsuringTitleUniqueness(entries.Pass(), callback); 1113 } 1114 1115 APIUtil::UploadKey APIUtil::RegisterUploadCallback( 1116 const UploadFileCallback& callback) { 1117 const bool inserted = upload_callback_map_.insert( 1118 std::make_pair(upload_next_key_, callback)).second; 1119 CHECK(inserted); 1120 return upload_next_key_++; 1121 } 1122 1123 APIUtil::UploadFileCallback APIUtil::GetAndUnregisterUploadCallback( 1124 UploadKey key) { 1125 UploadFileCallback callback; 1126 UploadCallbackMap::iterator found = upload_callback_map_.find(key); 1127 if (found == upload_callback_map_.end()) 1128 return callback; 1129 callback = found->second; 1130 upload_callback_map_.erase(found); 1131 return callback; 1132 } 1133 1134 void APIUtil::CancelAllUploads(google_apis::GDataErrorCode error) { 1135 if (upload_callback_map_.empty()) 1136 return; 1137 for (UploadCallbackMap::iterator iter = upload_callback_map_.begin(); 1138 iter != upload_callback_map_.end(); 1139 ++iter) { 1140 iter->second.Run(error, std::string(), std::string()); 1141 } 1142 upload_callback_map_.clear(); 1143 drive_uploader_.reset(new drive::DriveUploader( 1144 drive_service_.get(), content::BrowserThread::GetBlockingPool())); 1145 } 1146 1147 std::string APIUtil::GetRootResourceId() const { 1148 if (IsDriveAPIDisabled()) 1149 return drive_service_->GetRootResourceId(); 1150 return root_resource_id_; 1151 } 1152 1153 } // namespace drive_backend 1154 } // namespace sync_file_system 1155