| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/sync_file_system/drive_backend_v1/api_util.h" |
| |
| #include <algorithm> |
| #include <functional> |
| #include <sstream> |
| #include <string> |
| |
| #include "base/file_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "base/values.h" |
| #include "chrome/browser/drive/drive_api_service.h" |
| #include "chrome/browser/drive/drive_api_util.h" |
| #include "chrome/browser/drive/drive_uploader.h" |
| #include "chrome/browser/drive/gdata_wapi_service.h" |
| #include "chrome/browser/google_apis/drive_api_parser.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/profile_oauth2_token_service.h" |
| #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" |
| #include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h" |
| #include "chrome/browser/sync_file_system/logger.h" |
| #include "chrome/browser/sync_file_system/syncable_file_system_util.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/common/constants.h" |
| #include "net/base/mime_util.h" |
| |
| namespace sync_file_system { |
| namespace drive_backend { |
| |
| namespace { |
| |
| enum ParentType { |
| PARENT_TYPE_ROOT_OR_EMPTY, |
| PARENT_TYPE_DIRECTORY, |
| }; |
| |
| const char kSyncRootDirectoryName[] = "Chrome Syncable FileSystem"; |
| const char kSyncRootDirectoryNameDev[] = "Chrome Syncable FileSystem Dev"; |
| const char kMimeTypeOctetStream[] = "application/octet-stream"; |
| |
| const char kFakeAccountId[] = "test_user@gmail.com"; |
| const char kFakeServerBaseUrl[] = "https://fake_server/"; |
| const char kFakeDownloadServerBaseUrl[] = "https://fake_download_server/"; |
| |
| void EmptyGDataErrorCodeCallback(google_apis::GDataErrorCode error) {} |
| |
| bool HasParentLinkTo(const ScopedVector<google_apis::Link>& links, |
| const std::string& parent_resource_id, |
| ParentType parent_type) { |
| bool has_parent = false; |
| |
| for (ScopedVector<google_apis::Link>::const_iterator itr = links.begin(); |
| itr != links.end(); ++itr) { |
| if ((*itr)->type() == google_apis::Link::LINK_PARENT) { |
| has_parent = true; |
| if (drive::util::ExtractResourceIdFromUrl((*itr)->href()) == |
| parent_resource_id) |
| return true; |
| } |
| } |
| |
| return parent_type == PARENT_TYPE_ROOT_OR_EMPTY && !has_parent; |
| } |
| |
| struct TitleAndParentQuery |
| : std::unary_function<const google_apis::ResourceEntry*, bool> { |
| TitleAndParentQuery(const std::string& title, |
| const std::string& parent_resource_id, |
| ParentType parent_type) |
| : title(title), |
| parent_resource_id(parent_resource_id), |
| parent_type(parent_type) {} |
| |
| bool operator()(const google_apis::ResourceEntry* entry) const { |
| return entry->title() == title && |
| HasParentLinkTo(entry->links(), parent_resource_id, parent_type); |
| } |
| |
| const std::string& title; |
| const std::string& parent_resource_id; |
| ParentType parent_type; |
| }; |
| |
| void FilterEntriesByTitleAndParent( |
| ScopedVector<google_apis::ResourceEntry>* entries, |
| const std::string& title, |
| const std::string& parent_resource_id, |
| ParentType parent_type) { |
| typedef ScopedVector<google_apis::ResourceEntry>::iterator iterator; |
| iterator itr = std::partition(entries->begin(), |
| entries->end(), |
| TitleAndParentQuery(title, |
| parent_resource_id, |
| parent_type)); |
| entries->erase(itr, entries->end()); |
| } |
| |
| google_apis::ResourceEntry* GetDocumentByTitleAndParent( |
| const ScopedVector<google_apis::ResourceEntry>& entries, |
| const std::string& title, |
| const std::string& parent_resource_id, |
| ParentType parent_type) { |
| typedef ScopedVector<google_apis::ResourceEntry>::const_iterator iterator; |
| iterator found = |
| std::find_if(entries.begin(), |
| entries.end(), |
| TitleAndParentQuery(title, parent_resource_id, parent_type)); |
| if (found != entries.end()) |
| return *found; |
| return NULL; |
| } |
| |
| void EntryAdapterForEnsureTitleUniqueness( |
| scoped_ptr<google_apis::ResourceEntry> entry, |
| const APIUtil::EnsureUniquenessCallback& callback, |
| APIUtil::EnsureUniquenessStatus status, |
| google_apis::GDataErrorCode error) { |
| callback.Run(error, status, entry.Pass()); |
| } |
| |
| void UploadResultAdapter(const APIUtil::ResourceEntryCallback& callback, |
| google_apis::GDataErrorCode error, |
| const GURL& upload_location, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| callback.Run(error, entry.Pass()); |
| } |
| |
| std::string GetMimeTypeFromTitle(const std::string& title) { |
| base::FilePath::StringType extension = |
| base::FilePath::FromUTF8Unsafe(title).Extension(); |
| std::string mime_type; |
| if (extension.empty() || |
| !net::GetWellKnownMimeTypeFromExtension(extension.substr(1), &mime_type)) |
| return kMimeTypeOctetStream; |
| return mime_type; |
| } |
| |
| bool CreateTemporaryFile(const base::FilePath& dir_path, |
| webkit_blob::ScopedFile* temp_file) { |
| base::FilePath temp_file_path; |
| const bool success = file_util::CreateDirectory(dir_path) && |
| file_util::CreateTemporaryFileInDir(dir_path, &temp_file_path); |
| if (!success) |
| return success; |
| *temp_file = |
| webkit_blob::ScopedFile(temp_file_path, |
| webkit_blob::ScopedFile::DELETE_ON_SCOPE_OUT, |
| base::MessageLoopProxy::current().get()); |
| return success; |
| } |
| |
| } // namespace |
| |
| APIUtil::APIUtil(Profile* profile, |
| const base::FilePath& temp_dir_path) |
| : wapi_url_generator_( |
| GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction), |
| GURL(google_apis::GDataWapiUrlGenerator:: |
| kBaseDownloadUrlForProduction)), |
| drive_api_url_generator_( |
| GURL(google_apis::DriveApiUrlGenerator::kBaseUrlForProduction), |
| GURL(google_apis::DriveApiUrlGenerator:: |
| kBaseDownloadUrlForProduction)), |
| upload_next_key_(0), |
| temp_dir_path_(temp_dir_path) { |
| ProfileOAuth2TokenService* oauth_service = |
| ProfileOAuth2TokenServiceFactory::GetForProfile(profile); |
| if (IsDriveAPIDisabled()) { |
| drive_service_.reset(new drive::GDataWapiService( |
| oauth_service, |
| profile->GetRequestContext(), |
| content::BrowserThread::GetBlockingPool(), |
| GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction), |
| GURL(google_apis::GDataWapiUrlGenerator::kBaseDownloadUrlForProduction), |
| std::string() /* custom_user_agent */)); |
| } else { |
| drive_service_.reset(new drive::DriveAPIService( |
| oauth_service, |
| profile->GetRequestContext(), |
| content::BrowserThread::GetBlockingPool(), |
| GURL(google_apis::DriveApiUrlGenerator::kBaseUrlForProduction), |
| GURL(google_apis::DriveApiUrlGenerator::kBaseDownloadUrlForProduction), |
| GURL(google_apis::GDataWapiUrlGenerator::kBaseUrlForProduction), |
| std::string() /* custom_user_agent */)); |
| } |
| |
| drive_service_->Initialize(oauth_service->GetPrimaryAccountId()); |
| drive_service_->AddObserver(this); |
| net::NetworkChangeNotifier::AddConnectionTypeObserver(this); |
| |
| drive_uploader_.reset(new drive::DriveUploader( |
| drive_service_.get(), content::BrowserThread::GetBlockingPool())); |
| } |
| |
| scoped_ptr<APIUtil> APIUtil::CreateForTesting( |
| const base::FilePath& temp_dir_path, |
| scoped_ptr<drive::DriveServiceInterface> drive_service, |
| scoped_ptr<drive::DriveUploaderInterface> drive_uploader) { |
| return make_scoped_ptr(new APIUtil( |
| temp_dir_path, |
| GURL(kFakeServerBaseUrl), |
| GURL(kFakeDownloadServerBaseUrl), |
| drive_service.Pass(), |
| drive_uploader.Pass(), |
| kFakeAccountId)); |
| } |
| |
| APIUtil::APIUtil(const base::FilePath& temp_dir_path, |
| const GURL& base_url, |
| const GURL& base_download_url, |
| scoped_ptr<drive::DriveServiceInterface> drive_service, |
| scoped_ptr<drive::DriveUploaderInterface> drive_uploader, |
| const std::string& account_id) |
| : wapi_url_generator_(base_url, base_download_url), |
| drive_api_url_generator_(base_url, base_download_url), |
| upload_next_key_(0), |
| temp_dir_path_(temp_dir_path) { |
| drive_service_ = drive_service.Pass(); |
| drive_service_->Initialize(account_id); |
| drive_service_->AddObserver(this); |
| net::NetworkChangeNotifier::AddConnectionTypeObserver(this); |
| |
| drive_uploader_ = drive_uploader.Pass(); |
| } |
| |
| APIUtil::~APIUtil() { |
| DCHECK(CalledOnValidThread()); |
| net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); |
| drive_service_->RemoveObserver(this); |
| } |
| |
| void APIUtil::AddObserver(APIUtilObserver* observer) { |
| DCHECK(CalledOnValidThread()); |
| observers_.AddObserver(observer); |
| } |
| |
| void APIUtil::RemoveObserver(APIUtilObserver* observer) { |
| DCHECK(CalledOnValidThread()); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void APIUtil::GetDriveRootResourceId(const GDataErrorCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!IsDriveAPIDisabled()); |
| DVLOG(2) << "Getting resource id for Drive root"; |
| |
| drive_service_->GetAboutResource( |
| base::Bind(&APIUtil::DidGetDriveRootResourceId, AsWeakPtr(), callback)); |
| } |
| |
| void APIUtil::DidGetDriveRootResourceId( |
| const GDataErrorCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::AboutResource> about_resource) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on getting resource id for Drive root: " << error; |
| callback.Run(error); |
| return; |
| } |
| |
| DCHECK(about_resource); |
| root_resource_id_ = about_resource->root_folder_id(); |
| DCHECK(!root_resource_id_.empty()); |
| DVLOG(2) << "Got resource id for Drive root: " << root_resource_id_; |
| callback.Run(error); |
| } |
| |
| void APIUtil::GetDriveDirectoryForSyncRoot(const ResourceIdCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (GetRootResourceId().empty()) { |
| GetDriveRootResourceId( |
| base::Bind(&APIUtil::DidGetDriveRootResourceIdForGetSyncRoot, |
| AsWeakPtr(), callback)); |
| return; |
| } |
| |
| DVLOG(2) << "Getting Drive directory for SyncRoot"; |
| std::string directory_name(GetSyncRootDirectoryName()); |
| SearchByTitle(directory_name, |
| std::string(), |
| base::Bind(&APIUtil::DidGetDirectory, |
| AsWeakPtr(), |
| std::string(), |
| directory_name, |
| callback)); |
| } |
| |
| void APIUtil::DidGetDriveRootResourceIdForGetSyncRoot( |
| const ResourceIdCallback& callback, |
| google_apis::GDataErrorCode error) { |
| DCHECK(CalledOnValidThread()); |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on getting Drive directory for SyncRoot: " << error; |
| callback.Run(error, std::string()); |
| return; |
| } |
| GetDriveDirectoryForSyncRoot(callback); |
| } |
| |
| void APIUtil::GetDriveDirectoryForOrigin( |
| const std::string& sync_root_resource_id, |
| const GURL& origin, |
| const ResourceIdCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Getting Drive directory for Origin: " << origin; |
| |
| std::string directory_name(OriginToDirectoryTitle(origin)); |
| SearchByTitle(directory_name, |
| sync_root_resource_id, |
| base::Bind(&APIUtil::DidGetDirectory, |
| AsWeakPtr(), |
| sync_root_resource_id, |
| directory_name, |
| callback)); |
| } |
| |
| void APIUtil::DidGetDirectory(const std::string& parent_resource_id, |
| const std::string& directory_name, |
| const ResourceIdCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceList> feed) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(IsStringASCII(directory_name)); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on getting Drive directory: " << error; |
| callback.Run(error, std::string()); |
| return; |
| } |
| |
| std::string resource_id; |
| ParentType parent_type = PARENT_TYPE_DIRECTORY; |
| if (parent_resource_id.empty()) { |
| resource_id = GetRootResourceId(); |
| DCHECK(!resource_id.empty()); |
| parent_type = PARENT_TYPE_ROOT_OR_EMPTY; |
| } else { |
| resource_id = parent_resource_id; |
| } |
| std::string title(directory_name); |
| google_apis::ResourceEntry* entry = GetDocumentByTitleAndParent( |
| feed->entries(), title, resource_id, parent_type); |
| if (!entry) { |
| DVLOG(2) << "Directory not found. Creating: " << directory_name; |
| drive_service_->AddNewDirectory(resource_id, |
| directory_name, |
| base::Bind(&APIUtil::DidCreateDirectory, |
| AsWeakPtr(), |
| parent_resource_id, |
| title, |
| callback)); |
| return; |
| } |
| DVLOG(2) << "Found Drive directory."; |
| |
| // TODO(tzik): Handle error. |
| DCHECK_EQ(google_apis::ENTRY_KIND_FOLDER, entry->kind()); |
| DCHECK_EQ(directory_name, entry->title()); |
| |
| if (entry->title() == GetSyncRootDirectoryName()) |
| EnsureSyncRootIsNotInMyDrive(entry->resource_id()); |
| |
| callback.Run(error, entry->resource_id()); |
| } |
| |
| void APIUtil::DidCreateDirectory(const std::string& parent_resource_id, |
| const std::string& title, |
| const ResourceIdCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS && |
| error != google_apis::HTTP_CREATED) { |
| DVLOG(2) << "Error on creating Drive directory: " << error; |
| callback.Run(error, std::string()); |
| return; |
| } |
| DVLOG(2) << "Created Drive directory."; |
| |
| DCHECK(entry); |
| // Check if any other client creates a directory with same title. |
| EnsureTitleUniqueness( |
| parent_resource_id, |
| title, |
| base::Bind(&APIUtil::DidEnsureUniquenessForCreateDirectory, |
| AsWeakPtr(), |
| callback)); |
| } |
| |
| void APIUtil::DidEnsureUniquenessForCreateDirectory( |
| const ResourceIdCallback& callback, |
| google_apis::GDataErrorCode error, |
| EnsureUniquenessStatus status, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| callback.Run(error, std::string()); |
| return; |
| } |
| |
| if (status == NO_DUPLICATES_FOUND) |
| error = google_apis::HTTP_CREATED; |
| |
| DCHECK(entry) << "No entry: " << error; |
| |
| if (!entry->is_folder()) { |
| // TODO(kinuko): Fix this. http://crbug.com/237090 |
| util::Log( |
| logging::LOG_ERROR, |
| FROM_HERE, |
| "A file is left for CreateDirectory due to file-folder conflict!"); |
| callback.Run(google_apis::HTTP_CONFLICT, std::string()); |
| return; |
| } |
| |
| if (entry->title() == GetSyncRootDirectoryName()) |
| EnsureSyncRootIsNotInMyDrive(entry->resource_id()); |
| |
| callback.Run(error, entry->resource_id()); |
| } |
| |
| void APIUtil::GetLargestChangeStamp(const ChangeStampCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Getting largest change id"; |
| |
| drive_service_->GetAboutResource( |
| base::Bind(&APIUtil::DidGetLargestChangeStamp, AsWeakPtr(), callback)); |
| } |
| |
| void APIUtil::GetResourceEntry(const std::string& resource_id, |
| const ResourceEntryCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Getting ResourceEntry for: " << resource_id; |
| |
| drive_service_->GetResourceEntry( |
| resource_id, |
| base::Bind(&APIUtil::DidGetResourceEntry, AsWeakPtr(), callback)); |
| } |
| |
| void APIUtil::DidGetLargestChangeStamp( |
| const ChangeStampCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::AboutResource> about_resource) { |
| DCHECK(CalledOnValidThread()); |
| |
| int64 largest_change_id = 0; |
| if (error == google_apis::HTTP_SUCCESS) { |
| DCHECK(about_resource); |
| largest_change_id = about_resource->largest_change_id(); |
| root_resource_id_ = about_resource->root_folder_id(); |
| DVLOG(2) << "Got largest change id: " << largest_change_id; |
| } else { |
| DVLOG(2) << "Error on getting largest change id: " << error; |
| } |
| |
| callback.Run(error, largest_change_id); |
| } |
| |
| void APIUtil::SearchByTitle(const std::string& title, |
| const std::string& directory_resource_id, |
| const ResourceListCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!title.empty()); |
| DVLOG(2) << "Searching resources in the directory [" << directory_resource_id |
| << "] with title [" << title << "]"; |
| |
| drive_service_->SearchByTitle( |
| title, |
| directory_resource_id, |
| base::Bind(&APIUtil::DidGetResourceList, AsWeakPtr(), callback)); |
| } |
| |
| void APIUtil::ListFiles(const std::string& directory_resource_id, |
| const ResourceListCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Listing resources in the directory [" << directory_resource_id |
| << "]"; |
| |
| drive_service_->GetResourceListInDirectory(directory_resource_id, callback); |
| } |
| |
| void APIUtil::ListChanges(int64 start_changestamp, |
| const ResourceListCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Listing changes since: " << start_changestamp; |
| |
| drive_service_->GetChangeList( |
| start_changestamp, |
| base::Bind(&APIUtil::DidGetResourceList, AsWeakPtr(), callback)); |
| } |
| |
| void APIUtil::ContinueListing(const GURL& next_link, |
| const ResourceListCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Continue listing on feed: " << next_link.spec(); |
| |
| drive_service_->GetRemainingFileList( |
| next_link, |
| base::Bind(&APIUtil::DidGetResourceList, AsWeakPtr(), callback)); |
| } |
| |
| void APIUtil::DownloadFile(const std::string& resource_id, |
| const std::string& local_file_md5, |
| const DownloadFileCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!temp_dir_path_.empty()); |
| DVLOG(2) << "Downloading file [" << resource_id << "]"; |
| |
| scoped_ptr<webkit_blob::ScopedFile> temp_file(new webkit_blob::ScopedFile); |
| webkit_blob::ScopedFile* temp_file_ptr = temp_file.get(); |
| content::BrowserThread::PostTaskAndReplyWithResult( |
| content::BrowserThread::FILE, FROM_HERE, |
| base::Bind(&CreateTemporaryFile, temp_dir_path_, temp_file_ptr), |
| base::Bind(&APIUtil::DidGetTemporaryFileForDownload, |
| AsWeakPtr(), resource_id, local_file_md5, |
| base::Passed(&temp_file), callback)); |
| } |
| |
| void APIUtil::UploadNewFile(const std::string& directory_resource_id, |
| const base::FilePath& local_file_path, |
| const std::string& title, |
| const UploadFileCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Uploading new file into the directory [" << directory_resource_id |
| << "] with title [" << title << "]"; |
| |
| std::string mime_type = GetMimeTypeFromTitle(title); |
| UploadKey upload_key = RegisterUploadCallback(callback); |
| ResourceEntryCallback did_upload_callback = |
| base::Bind(&APIUtil::DidUploadNewFile, |
| AsWeakPtr(), |
| directory_resource_id, |
| title, |
| upload_key); |
| drive_uploader_->UploadNewFile( |
| directory_resource_id, |
| local_file_path, |
| title, |
| mime_type, |
| base::Bind(&UploadResultAdapter, did_upload_callback), |
| google_apis::ProgressCallback()); |
| } |
| |
| void APIUtil::UploadExistingFile(const std::string& resource_id, |
| const std::string& remote_file_md5, |
| const base::FilePath& local_file_path, |
| const UploadFileCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Uploading existing file [" << resource_id << "]"; |
| drive_service_->GetResourceEntry( |
| resource_id, |
| base::Bind(&APIUtil::DidGetResourceEntry, |
| AsWeakPtr(), |
| base::Bind(&APIUtil::UploadExistingFileInternal, |
| AsWeakPtr(), |
| remote_file_md5, |
| local_file_path, |
| callback))); |
| } |
| |
| void APIUtil::CreateDirectory(const std::string& parent_resource_id, |
| const std::string& title, |
| const ResourceIdCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| // TODO(kinuko): This will call EnsureTitleUniqueness and will delete |
| // directories if there're duplicated directories. This must be ok |
| // for current design but we'll need to merge directories when we support |
| // 'real' directories. |
| drive_service_->AddNewDirectory(parent_resource_id, |
| title, |
| base::Bind(&APIUtil::DidCreateDirectory, |
| AsWeakPtr(), |
| parent_resource_id, |
| title, |
| callback)); |
| } |
| |
| void APIUtil::DeleteFile(const std::string& resource_id, |
| const std::string& remote_file_md5, |
| const GDataErrorCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Deleting file: " << resource_id; |
| |
| // Load actual remote_file_md5 to check for conflict before deletion. |
| if (!remote_file_md5.empty()) { |
| drive_service_->GetResourceEntry( |
| resource_id, |
| base::Bind(&APIUtil::DidGetResourceEntry, |
| AsWeakPtr(), |
| base::Bind(&APIUtil::DeleteFileInternal, |
| AsWeakPtr(), |
| remote_file_md5, |
| callback))); |
| return; |
| } |
| |
| // Expected remote_file_md5 is empty so do a force delete. |
| drive_service_->DeleteResource( |
| resource_id, |
| std::string(), |
| base::Bind(&APIUtil::DidDeleteFile, AsWeakPtr(), callback)); |
| return; |
| } |
| |
| GURL APIUtil::ResourceIdToResourceLink(const std::string& resource_id) const { |
| return IsDriveAPIDisabled() |
| ? wapi_url_generator_.GenerateEditUrl(resource_id) |
| : drive_api_url_generator_.GetFilesGetUrl(resource_id); |
| } |
| |
| void APIUtil::EnsureSyncRootIsNotInMyDrive( |
| const std::string& sync_root_resource_id) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (GetRootResourceId().empty()) { |
| GetDriveRootResourceId( |
| base::Bind(&APIUtil::DidGetDriveRootResourceIdForEnsureSyncRoot, |
| AsWeakPtr(), sync_root_resource_id)); |
| return; |
| } |
| |
| DVLOG(2) << "Ensuring the sync root directory is not in 'My Drive'."; |
| drive_service_->RemoveResourceFromDirectory( |
| GetRootResourceId(), |
| sync_root_resource_id, |
| base::Bind(&EmptyGDataErrorCodeCallback)); |
| } |
| |
| void APIUtil::DidGetDriveRootResourceIdForEnsureSyncRoot( |
| const std::string& sync_root_resource_id, |
| google_apis::GDataErrorCode error) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on ensuring the sync root directory is not in" |
| << " 'My Drive': " << error; |
| // Give up ensuring the sync root directory is not in 'My Drive'. This will |
| // be retried at some point. |
| return; |
| } |
| |
| DCHECK(!GetRootResourceId().empty()); |
| EnsureSyncRootIsNotInMyDrive(sync_root_resource_id); |
| } |
| |
| // static |
| // TODO(calvinlo): Delete this when Sync Directory Operations are supported by |
| // default. |
| std::string APIUtil::GetSyncRootDirectoryName() { |
| return IsSyncFSDirectoryOperationEnabled() ? kSyncRootDirectoryNameDev |
| : kSyncRootDirectoryName; |
| } |
| |
| // static |
| std::string APIUtil::OriginToDirectoryTitle(const GURL& origin) { |
| DCHECK(origin.SchemeIs(extensions::kExtensionScheme)); |
| return origin.host(); |
| } |
| |
| // static |
| GURL APIUtil::DirectoryTitleToOrigin(const std::string& title) { |
| return extensions::Extension::GetBaseURLFromExtensionId(title); |
| } |
| |
| void APIUtil::OnReadyToSendRequests() { |
| DCHECK(CalledOnValidThread()); |
| FOR_EACH_OBSERVER(APIUtilObserver, observers_, OnAuthenticated()); |
| } |
| |
| void APIUtil::OnConnectionTypeChanged( |
| net::NetworkChangeNotifier::ConnectionType type) { |
| DCHECK(CalledOnValidThread()); |
| if (type != net::NetworkChangeNotifier::CONNECTION_NONE) { |
| FOR_EACH_OBSERVER(APIUtilObserver, observers_, OnNetworkConnected()); |
| return; |
| } |
| // We're now disconnected, reset the drive_uploader_ to force stop |
| // uploading, otherwise the uploader may get stuck. |
| // TODO(kinuko): Check the uploader behavior if it's the expected behavior |
| // (http://crbug.com/223818) |
| CancelAllUploads(google_apis::GDATA_NO_CONNECTION); |
| } |
| |
| void APIUtil::DidGetResourceList( |
| const ResourceListCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceList> resource_list) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on listing resource: " << error; |
| callback.Run(error, scoped_ptr<google_apis::ResourceList>()); |
| return; |
| } |
| |
| DVLOG(2) << "Got resource list"; |
| DCHECK(resource_list); |
| callback.Run(error, resource_list.Pass()); |
| } |
| |
| void APIUtil::DidGetResourceEntry( |
| const ResourceEntryCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on getting resource entry:" << error; |
| callback.Run(error, scoped_ptr<google_apis::ResourceEntry>()); |
| return; |
| } |
| |
| if (entry->deleted()) { |
| DVLOG(2) << "Got resource entry, the entry was trashed."; |
| callback.Run(google_apis::HTTP_NOT_FOUND, entry.Pass()); |
| return; |
| } |
| |
| DVLOG(2) << "Got resource entry"; |
| DCHECK(entry); |
| callback.Run(error, entry.Pass()); |
| } |
| |
| void APIUtil::DidGetTemporaryFileForDownload( |
| const std::string& resource_id, |
| const std::string& local_file_md5, |
| scoped_ptr<webkit_blob::ScopedFile> local_file, |
| const DownloadFileCallback& callback, |
| bool success) { |
| if (!success) { |
| DVLOG(2) << "Error in creating a temp file under " |
| << temp_dir_path_.value(); |
| callback.Run(google_apis::GDATA_FILE_ERROR, std::string(), 0, base::Time(), |
| local_file->Pass()); |
| return; |
| } |
| drive_service_->GetResourceEntry( |
| resource_id, |
| base::Bind(&APIUtil::DidGetResourceEntry, |
| AsWeakPtr(), |
| base::Bind(&APIUtil::DownloadFileInternal, |
| AsWeakPtr(), |
| local_file_md5, |
| base::Passed(&local_file), |
| callback))); |
| } |
| |
| void APIUtil::DownloadFileInternal( |
| const std::string& local_file_md5, |
| scoped_ptr<webkit_blob::ScopedFile> local_file, |
| const DownloadFileCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on getting resource entry for download"; |
| callback.Run(error, std::string(), 0, base::Time(), local_file->Pass()); |
| return; |
| } |
| DCHECK(entry); |
| |
| DVLOG(2) << "Got resource entry for download"; |
| // If local file and remote file are same, cancel the download. |
| if (local_file_md5 == entry->file_md5()) { |
| callback.Run(google_apis::HTTP_NOT_MODIFIED, |
| local_file_md5, |
| entry->file_size(), |
| entry->updated_time(), |
| local_file->Pass()); |
| return; |
| } |
| |
| DVLOG(2) << "Downloading file: " << entry->resource_id(); |
| const std::string& resource_id = entry->resource_id(); |
| const base::FilePath& local_file_path = local_file->path(); |
| drive_service_->DownloadFile(local_file_path, |
| resource_id, |
| base::Bind(&APIUtil::DidDownloadFile, |
| AsWeakPtr(), |
| base::Passed(&entry), |
| base::Passed(&local_file), |
| callback), |
| google_apis::GetContentCallback(), |
| google_apis::ProgressCallback()); |
| } |
| |
| void APIUtil::DidDownloadFile(scoped_ptr<google_apis::ResourceEntry> entry, |
| scoped_ptr<webkit_blob::ScopedFile> local_file, |
| const DownloadFileCallback& callback, |
| google_apis::GDataErrorCode error, |
| const base::FilePath& downloaded_file_path) { |
| DCHECK(CalledOnValidThread()); |
| if (error == google_apis::HTTP_SUCCESS) |
| DVLOG(2) << "Download completed"; |
| else |
| DVLOG(2) << "Error on downloading file: " << error; |
| |
| callback.Run( |
| error, entry->file_md5(), entry->file_size(), entry->updated_time(), |
| local_file->Pass()); |
| } |
| |
| void APIUtil::DidUploadNewFile(const std::string& parent_resource_id, |
| const std::string& title, |
| UploadKey upload_key, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| UploadFileCallback callback = GetAndUnregisterUploadCallback(upload_key); |
| DCHECK(!callback.is_null()); |
| if (error != google_apis::HTTP_SUCCESS && |
| error != google_apis::HTTP_CREATED) { |
| DVLOG(2) << "Error on uploading new file: " << error; |
| callback.Run(error, std::string(), std::string()); |
| return; |
| } |
| |
| DVLOG(2) << "Upload completed"; |
| EnsureTitleUniqueness(parent_resource_id, |
| title, |
| base::Bind(&APIUtil::DidEnsureUniquenessForCreateFile, |
| AsWeakPtr(), |
| entry->resource_id(), |
| callback)); |
| } |
| |
| void APIUtil::DidEnsureUniquenessForCreateFile( |
| const std::string& expected_resource_id, |
| const UploadFileCallback& callback, |
| google_apis::GDataErrorCode error, |
| EnsureUniquenessStatus status, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on uploading new file: " << error; |
| callback.Run(error, std::string(), std::string()); |
| return; |
| } |
| |
| switch (status) { |
| case NO_DUPLICATES_FOUND: |
| // The file was uploaded successfully and no conflict was detected. |
| DCHECK(entry); |
| DVLOG(2) << "No conflict detected on uploading new file"; |
| callback.Run( |
| google_apis::HTTP_CREATED, entry->resource_id(), entry->file_md5()); |
| return; |
| |
| case RESOLVED_DUPLICATES: |
| // The file was uploaded successfully but a conflict was detected. |
| // The duplicated file was deleted successfully. |
| DCHECK(entry); |
| if (entry->resource_id() != expected_resource_id) { |
| // TODO(kinuko): We should check local vs remote md5 here. |
| DVLOG(2) << "Conflict detected on uploading new file"; |
| callback.Run(google_apis::HTTP_CONFLICT, |
| entry->resource_id(), |
| entry->file_md5()); |
| return; |
| } |
| |
| DVLOG(2) << "Conflict detected on uploading new file and resolved"; |
| callback.Run( |
| google_apis::HTTP_CREATED, entry->resource_id(), entry->file_md5()); |
| return; |
| |
| default: |
| NOTREACHED() << "Unknown status from EnsureTitleUniqueness:" << status |
| << " for " << expected_resource_id; |
| } |
| } |
| |
| void APIUtil::UploadExistingFileInternal( |
| const std::string& remote_file_md5, |
| const base::FilePath& local_file_path, |
| const UploadFileCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on uploading existing file: " << error; |
| callback.Run(error, std::string(), std::string()); |
| return; |
| } |
| DCHECK(entry); |
| |
| // If remote file's hash value is different from the expected one, conflict |
| // might have occurred. |
| if (!remote_file_md5.empty() && remote_file_md5 != entry->file_md5()) { |
| DVLOG(2) << "Conflict detected before uploading existing file"; |
| callback.Run(google_apis::HTTP_CONFLICT, std::string(), std::string()); |
| return; |
| } |
| |
| std::string mime_type = GetMimeTypeFromTitle(entry->title()); |
| UploadKey upload_key = RegisterUploadCallback(callback); |
| ResourceEntryCallback did_upload_callback = |
| base::Bind(&APIUtil::DidUploadExistingFile, AsWeakPtr(), upload_key); |
| drive_uploader_->UploadExistingFile( |
| entry->resource_id(), |
| local_file_path, |
| mime_type, |
| entry->etag(), |
| base::Bind(&UploadResultAdapter, did_upload_callback), |
| google_apis::ProgressCallback()); |
| } |
| |
| bool APIUtil::IsAuthenticated() const { |
| return drive_service_->HasRefreshToken(); |
| } |
| |
| void APIUtil::DidUploadExistingFile( |
| UploadKey upload_key, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| DCHECK(CalledOnValidThread()); |
| UploadFileCallback callback = GetAndUnregisterUploadCallback(upload_key); |
| DCHECK(!callback.is_null()); |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on uploading existing file: " << error; |
| callback.Run(error, std::string(), std::string()); |
| return; |
| } |
| |
| DCHECK(entry); |
| DVLOG(2) << "Upload completed"; |
| callback.Run(error, entry->resource_id(), entry->file_md5()); |
| } |
| |
| void APIUtil::DeleteFileInternal(const std::string& remote_file_md5, |
| const GDataErrorCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceEntry> entry) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on getting resource entry for deleting file: " << error; |
| callback.Run(error); |
| return; |
| } |
| DCHECK(entry); |
| |
| // If remote file's hash value is different from the expected one, conflict |
| // might have occurred. |
| if (!remote_file_md5.empty() && remote_file_md5 != entry->file_md5()) { |
| DVLOG(2) << "Conflict detected before deleting file"; |
| callback.Run(google_apis::HTTP_CONFLICT); |
| return; |
| } |
| DVLOG(2) << "Got resource entry for deleting file"; |
| |
| // Move the file to trash (don't delete it completely). |
| drive_service_->DeleteResource( |
| entry->resource_id(), |
| entry->etag(), |
| base::Bind(&APIUtil::DidDeleteFile, AsWeakPtr(), callback)); |
| } |
| |
| void APIUtil::DidDeleteFile(const GDataErrorCallback& callback, |
| google_apis::GDataErrorCode error) { |
| DCHECK(CalledOnValidThread()); |
| if (error == google_apis::HTTP_SUCCESS) |
| DVLOG(2) << "Deletion completed"; |
| else |
| DVLOG(2) << "Error on deleting file: " << error; |
| |
| callback.Run(error); |
| } |
| |
| void APIUtil::EnsureTitleUniqueness(const std::string& parent_resource_id, |
| const std::string& expected_title, |
| const EnsureUniquenessCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Checking if there's no conflict on entry creation"; |
| |
| const google_apis::GetResourceListCallback& bound_callback = |
| base::Bind(&APIUtil::DidListEntriesToEnsureUniqueness, |
| AsWeakPtr(), |
| parent_resource_id, |
| expected_title, |
| callback); |
| |
| SearchByTitle(expected_title, parent_resource_id, bound_callback); |
| } |
| |
| void APIUtil::DidListEntriesToEnsureUniqueness( |
| const std::string& parent_resource_id, |
| const std::string& expected_title, |
| const EnsureUniquenessCallback& callback, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::ResourceList> feed) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS) { |
| DVLOG(2) << "Error on listing resource for ensuring title uniqueness"; |
| callback.Run( |
| error, NO_DUPLICATES_FOUND, scoped_ptr<google_apis::ResourceEntry>()); |
| return; |
| } |
| DVLOG(2) << "Got resource list for ensuring title uniqueness"; |
| |
| // This filtering is needed only on WAPI. Once we move to Drive API we can |
| // drop this. |
| std::string resource_id; |
| ParentType parent_type = PARENT_TYPE_DIRECTORY; |
| if (parent_resource_id.empty()) { |
| resource_id = GetRootResourceId(); |
| DCHECK(!resource_id.empty()); |
| parent_type = PARENT_TYPE_ROOT_OR_EMPTY; |
| } else { |
| resource_id = parent_resource_id; |
| } |
| ScopedVector<google_apis::ResourceEntry> entries; |
| entries.swap(*feed->mutable_entries()); |
| FilterEntriesByTitleAndParent( |
| &entries, expected_title, resource_id, parent_type); |
| |
| if (entries.empty()) { |
| DVLOG(2) << "Uploaded file is not found"; |
| callback.Run(google_apis::HTTP_NOT_FOUND, |
| NO_DUPLICATES_FOUND, |
| scoped_ptr<google_apis::ResourceEntry>()); |
| return; |
| } |
| |
| if (entries.size() >= 2) { |
| DVLOG(2) << "Conflict detected on creating entry"; |
| for (size_t i = 0; i < entries.size() - 1; ++i) { |
| // TODO(tzik): Replace published_time with creation time after we move to |
| // Drive API. |
| if (entries[i]->published_time() < entries.back()->published_time()) |
| std::swap(entries[i], entries.back()); |
| } |
| |
| scoped_ptr<google_apis::ResourceEntry> earliest_entry(entries.back()); |
| entries.back() = NULL; |
| entries.get().pop_back(); |
| |
| DeleteEntriesForEnsuringTitleUniqueness( |
| entries.Pass(), |
| base::Bind(&EntryAdapterForEnsureTitleUniqueness, |
| base::Passed(&earliest_entry), |
| callback, |
| RESOLVED_DUPLICATES)); |
| return; |
| } |
| |
| DVLOG(2) << "no conflict detected"; |
| DCHECK_EQ(1u, entries.size()); |
| scoped_ptr<google_apis::ResourceEntry> entry(entries.front()); |
| entries.weak_clear(); |
| |
| callback.Run(google_apis::HTTP_SUCCESS, NO_DUPLICATES_FOUND, entry.Pass()); |
| } |
| |
| void APIUtil::DeleteEntriesForEnsuringTitleUniqueness( |
| ScopedVector<google_apis::ResourceEntry> entries, |
| const GDataErrorCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DVLOG(2) << "Cleaning up conflict on entry creation"; |
| |
| if (entries.empty()) { |
| callback.Run(google_apis::HTTP_SUCCESS); |
| return; |
| } |
| |
| scoped_ptr<google_apis::ResourceEntry> entry(entries.back()); |
| entries.back() = NULL; |
| entries.get().pop_back(); |
| |
| // We don't care conflicts here as other clients may be also deleting this |
| // file, so passing an empty etag. |
| drive_service_->DeleteResource( |
| entry->resource_id(), |
| std::string(), // empty etag |
| base::Bind(&APIUtil::DidDeleteEntriesForEnsuringTitleUniqueness, |
| AsWeakPtr(), |
| base::Passed(&entries), |
| callback)); |
| } |
| |
| void APIUtil::DidDeleteEntriesForEnsuringTitleUniqueness( |
| ScopedVector<google_apis::ResourceEntry> entries, |
| const GDataErrorCallback& callback, |
| google_apis::GDataErrorCode error) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (error != google_apis::HTTP_SUCCESS && |
| error != google_apis::HTTP_NOT_FOUND) { |
| DVLOG(2) << "Error on deleting file: " << error; |
| callback.Run(error); |
| return; |
| } |
| |
| DVLOG(2) << "Deletion completed"; |
| DeleteEntriesForEnsuringTitleUniqueness(entries.Pass(), callback); |
| } |
| |
| APIUtil::UploadKey APIUtil::RegisterUploadCallback( |
| const UploadFileCallback& callback) { |
| const bool inserted = upload_callback_map_.insert( |
| std::make_pair(upload_next_key_, callback)).second; |
| CHECK(inserted); |
| return upload_next_key_++; |
| } |
| |
| APIUtil::UploadFileCallback APIUtil::GetAndUnregisterUploadCallback( |
| UploadKey key) { |
| UploadFileCallback callback; |
| UploadCallbackMap::iterator found = upload_callback_map_.find(key); |
| if (found == upload_callback_map_.end()) |
| return callback; |
| callback = found->second; |
| upload_callback_map_.erase(found); |
| return callback; |
| } |
| |
| void APIUtil::CancelAllUploads(google_apis::GDataErrorCode error) { |
| if (upload_callback_map_.empty()) |
| return; |
| for (UploadCallbackMap::iterator iter = upload_callback_map_.begin(); |
| iter != upload_callback_map_.end(); |
| ++iter) { |
| iter->second.Run(error, std::string(), std::string()); |
| } |
| upload_callback_map_.clear(); |
| drive_uploader_.reset(new drive::DriveUploader( |
| drive_service_.get(), content::BrowserThread::GetBlockingPool())); |
| } |
| |
| std::string APIUtil::GetRootResourceId() const { |
| if (IsDriveAPIDisabled()) |
| return drive_service_->GetRootResourceId(); |
| return root_resource_id_; |
| } |
| |
| } // namespace drive_backend |
| } // namespace sync_file_system |