blob: a5b08dfd8b5b5c1c20f8734502263545edf7eced [file] [log] [blame]
// 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 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) {
OAuth2TokenService* 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();
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()));
}
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)
: 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();
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