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