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