root/chrome/browser/drive/drive_api_service.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. DidConvertToResourceListOnBlockingPool
  2. ConvertFileEntryToResourceEntryAndRun
  3. ConvertFileListToResourceList
  4. ConvertFileListToResourceListOnBlockingPoolAndRun
  5. ConvertChangeListToResourceList
  6. ConvertChangeListToResourceListOnBlockingPoolAndRun
  7. ConvertFileResourceToResourceEntryForUploadRangeAndRun
  8. ExtractOpenUrlAndRun
  9. EntryActionCallbackAdapter
  10. custom_user_agent_
  11. Initialize
  12. AddObserver
  13. RemoveObserver
  14. CanSendRequest
  15. GetResourceIdCanonicalizer
  16. GetRootResourceId
  17. GetAllResourceList
  18. GetResourceListInDirectory
  19. Search
  20. SearchByTitle
  21. GetChangeList
  22. GetRemainingChangeList
  23. GetRemainingFileList
  24. GetResourceEntry
  25. GetShareUrl
  26. GetAboutResource
  27. GetAppList
  28. DownloadFile
  29. DeleteResource
  30. TrashResource
  31. AddNewDirectory
  32. CopyResource
  33. UpdateResource
  34. RenameResource
  35. AddResourceToDirectory
  36. RemoveResourceFromDirectory
  37. InitiateUploadNewFile
  38. InitiateUploadExistingFile
  39. ResumeUpload
  40. GetUploadStatus
  41. AuthorizeApp
  42. UninstallApp
  43. AddPermission
  44. HasAccessToken
  45. RequestAccessToken
  46. HasRefreshToken
  47. ClearAccessToken
  48. ClearRefreshToken
  49. OnOAuth2RefreshTokenChanged

// Copyright (c) 2012 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/drive/drive_api_service.h"

#include <string>
#include <vector>

#include "base/bind.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/task_runner_util.h"
#include "base/values.h"
#include "chrome/browser/drive/drive_api_util.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/drive/auth_service.h"
#include "google_apis/drive/drive_api_parser.h"
#include "google_apis/drive/drive_api_requests.h"
#include "google_apis/drive/gdata_errorcode.h"
#include "google_apis/drive/gdata_wapi_parser.h"
#include "google_apis/drive/gdata_wapi_requests.h"
#include "google_apis/drive/request_sender.h"
#include "google_apis/google_api_keys.h"
#include "net/url_request/url_request_context_getter.h"

using content::BrowserThread;
using google_apis::AppList;
using google_apis::AppListCallback;
using google_apis::AuthStatusCallback;
using google_apis::AuthorizeAppCallback;
using google_apis::CancelCallback;
using google_apis::ChangeList;
using google_apis::DownloadActionCallback;
using google_apis::EntryActionCallback;
using google_apis::FileList;
using google_apis::FileResource;
using google_apis::GDATA_OTHER_ERROR;
using google_apis::GDATA_PARSE_ERROR;
using google_apis::GDataErrorCode;
using google_apis::AboutResourceCallback;
using google_apis::GetContentCallback;
using google_apis::GetResourceEntryCallback;
using google_apis::GetResourceEntryRequest;
using google_apis::GetResourceListCallback;
using google_apis::GetShareUrlCallback;
using google_apis::HTTP_NOT_IMPLEMENTED;
using google_apis::HTTP_SUCCESS;
using google_apis::InitiateUploadCallback;
using google_apis::Link;
using google_apis::ProgressCallback;
using google_apis::RequestSender;
using google_apis::ResourceEntry;
using google_apis::ResourceList;
using google_apis::UploadRangeCallback;
using google_apis::UploadRangeResponse;
using google_apis::drive::AboutGetRequest;
using google_apis::drive::AppsListRequest;
using google_apis::drive::ChangesListRequest;
using google_apis::drive::ChangesListNextPageRequest;
using google_apis::drive::ChildrenDeleteRequest;
using google_apis::drive::ChildrenInsertRequest;
using google_apis::drive::DownloadFileRequest;
using google_apis::drive::FilesCopyRequest;
using google_apis::drive::FilesGetRequest;
using google_apis::drive::FilesInsertRequest;
using google_apis::drive::FilesPatchRequest;
using google_apis::drive::FilesListRequest;
using google_apis::drive::FilesListNextPageRequest;
using google_apis::drive::FilesDeleteRequest;
using google_apis::drive::FilesTrashRequest;
using google_apis::drive::GetUploadStatusRequest;
using google_apis::drive::InitiateUploadExistingFileRequest;
using google_apis::drive::InitiateUploadNewFileRequest;
using google_apis::drive::ResumeUploadRequest;

namespace drive {

namespace {

// OAuth2 scopes for Drive API.
const char kDriveScope[] = "https://www.googleapis.com/auth/drive";
const char kDriveAppsReadonlyScope[] =
    "https://www.googleapis.com/auth/drive.apps.readonly";

// Mime type to create a directory.
const char kFolderMimeType[] = "application/vnd.google-apps.folder";

// Max number of file entries to be fetched in a single http request.
//
// The larger the number is,
// - The total running time to fetch the whole file list will become shorter.
// - The running time for a single request tends to become longer.
// Since the file list fetching is a completely background task, for our side,
// only the total time matters. However, the server seems to have a time limit
// per single request, which disables us to set the largest value (1000).
// TODO(kinaba): make it larger when the server gets faster.
const int kMaxNumFilesResourcePerRequest = 300;
const int kMaxNumFilesResourcePerRequestForSearch = 100;

// For performance, we declare all fields we use.
const char kAboutResourceFields[] =
    "kind,quotaBytesTotal,quotaBytesUsed,largestChangeId,rootFolderId";
const char kFileResourceFields[] =
    "kind,id,title,createdDate,sharedWithMeDate,mimeType,"
    "md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width,"
    "imageMediaMetadata/height,imageMediaMetadata/rotation,etag,"
    "parents/parentLink,alternateLink,"
    "modifiedDate,lastViewedByMeDate,shared";
const char kFileResourceOpenWithLinksFields[] =
    "kind,id,openWithLinks/*";
const char kFileListFields[] =
    "kind,items(kind,id,title,createdDate,sharedWithMeDate,"
    "mimeType,md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width,"
    "imageMediaMetadata/height,imageMediaMetadata/rotation,etag,"
    "parents/parentLink,alternateLink,"
    "modifiedDate,lastViewedByMeDate,shared),nextLink";
const char kChangeListFields[] =
    "kind,items(file(kind,id,title,createdDate,sharedWithMeDate,"
    "mimeType,md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width,"
    "imageMediaMetadata/height,imageMediaMetadata/rotation,etag,"
    "parents/parentLink,alternateLink,modifiedDate,lastViewedByMeDate,shared),"
    "deleted,id,fileId,modificationDate),nextLink,largestChangeId";

// Callback invoked when the parsing of resource list is completed,
// regardless whether it is succeeded or not.
void DidConvertToResourceListOnBlockingPool(
    const GetResourceListCallback& callback,
    scoped_ptr<ResourceList> resource_list) {
  GDataErrorCode error = resource_list ? HTTP_SUCCESS : GDATA_PARSE_ERROR;
  callback.Run(error, resource_list.Pass());
}

// Converts the FileResource value to ResourceEntry and runs |callback| on the
// UI thread.
void ConvertFileEntryToResourceEntryAndRun(
    const GetResourceEntryCallback& callback,
    GDataErrorCode error,
    scoped_ptr<FileResource> value) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(error, scoped_ptr<ResourceEntry>());
    return;
  }

  // Converting to ResourceEntry is cheap enough to do on UI thread.
  scoped_ptr<ResourceEntry> entry =
      util::ConvertFileResourceToResourceEntry(*value);
  if (!entry) {
    callback.Run(GDATA_PARSE_ERROR, scoped_ptr<ResourceEntry>());
    return;
  }

  callback.Run(error, entry.Pass());
}

// Thin adapter of ConvertFileListToResourceList.
scoped_ptr<ResourceList> ConvertFileListToResourceList(
    scoped_ptr<FileList> file_list) {
  return util::ConvertFileListToResourceList(*file_list);
}

// Converts the FileList value to ResourceList on blocking pool and runs
// |callback| on the UI thread.
void ConvertFileListToResourceListOnBlockingPoolAndRun(
    scoped_refptr<base::TaskRunner> blocking_task_runner,
    const GetResourceListCallback& callback,
    GDataErrorCode error,
    scoped_ptr<FileList> value) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(error, scoped_ptr<ResourceList>());
    return;
  }

  // Convert the value on blocking pool.
  base::PostTaskAndReplyWithResult(
      blocking_task_runner.get(),
      FROM_HERE,
      base::Bind(&ConvertFileListToResourceList, base::Passed(&value)),
      base::Bind(&DidConvertToResourceListOnBlockingPool, callback));
}

// Thin adapter of ConvertChangeListToResourceList.
scoped_ptr<ResourceList> ConvertChangeListToResourceList(
    scoped_ptr<ChangeList> change_list) {
  return util::ConvertChangeListToResourceList(*change_list);
}

// Converts the FileList value to ResourceList on blocking pool and runs
// |callback| on the UI thread.
void ConvertChangeListToResourceListOnBlockingPoolAndRun(
    scoped_refptr<base::TaskRunner> blocking_task_runner,
    const GetResourceListCallback& callback,
    GDataErrorCode error,
    scoped_ptr<ChangeList> value) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(error, scoped_ptr<ResourceList>());
    return;
  }

  // Convert the value on blocking pool.
  base::PostTaskAndReplyWithResult(
      blocking_task_runner.get(),
      FROM_HERE,
      base::Bind(&ConvertChangeListToResourceList, base::Passed(&value)),
      base::Bind(&DidConvertToResourceListOnBlockingPool, callback));
}

// Converts the FileResource value to ResourceEntry for upload range request,
// and runs |callback| on the UI thread.
void ConvertFileResourceToResourceEntryForUploadRangeAndRun(
    const UploadRangeCallback& callback,
    const UploadRangeResponse& response,
    scoped_ptr<FileResource> value) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(response, scoped_ptr<ResourceEntry>());
    return;
  }

  // Converting to ResourceEntry is cheap enough to do on UI thread.
  scoped_ptr<ResourceEntry> entry =
      util::ConvertFileResourceToResourceEntry(*value);
  if (!entry) {
    callback.Run(UploadRangeResponse(GDATA_PARSE_ERROR,
                                     response.start_position_received,
                                     response.end_position_received),
                 scoped_ptr<ResourceEntry>());
    return;
  }

  callback.Run(response, entry.Pass());
}

void ExtractOpenUrlAndRun(const std::string& app_id,
                          const AuthorizeAppCallback& callback,
                          GDataErrorCode error,
                          scoped_ptr<FileResource> value) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  if (!value) {
    callback.Run(error, GURL());
    return;
  }

  const std::vector<FileResource::OpenWithLink>& open_with_links =
      value->open_with_links();
  for (size_t i = 0; i < open_with_links.size(); ++i) {
    if (open_with_links[i].app_id == app_id) {
      callback.Run(HTTP_SUCCESS, open_with_links[i].open_url);
      return;
    }
  }

  // Not found.
  callback.Run(GDATA_OTHER_ERROR, GURL());
}

// Ignores the |entry|, and runs the |callback|.
void EntryActionCallbackAdapter(
    const EntryActionCallback& callback,
    GDataErrorCode error, scoped_ptr<FileResource> entry) {
  callback.Run(error);
}

// The resource ID for the root directory for Drive API is defined in the spec:
// https://developers.google.com/drive/folder
const char kDriveApiRootDirectoryResourceId[] = "root";

}  // namespace

DriveAPIService::DriveAPIService(
    OAuth2TokenService* oauth2_token_service,
    net::URLRequestContextGetter* url_request_context_getter,
    base::SequencedTaskRunner* blocking_task_runner,
    const GURL& base_url,
    const GURL& base_download_url,
    const GURL& wapi_base_url,
    const std::string& custom_user_agent)
    : oauth2_token_service_(oauth2_token_service),
      url_request_context_getter_(url_request_context_getter),
      blocking_task_runner_(blocking_task_runner),
      url_generator_(base_url, base_download_url),
      wapi_url_generator_(wapi_base_url, base_download_url),
      custom_user_agent_(custom_user_agent) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}

DriveAPIService::~DriveAPIService() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (sender_.get())
    sender_->auth_service()->RemoveObserver(this);
}

void DriveAPIService::Initialize(const std::string& account_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  std::vector<std::string> scopes;
  scopes.push_back(kDriveScope);
  scopes.push_back(kDriveAppsReadonlyScope);
  scopes.push_back(util::kDriveAppsScope);

  // GData WAPI token for GetShareUrl() and GetResourceListInDirectoryByWapi().
  scopes.push_back(util::kDocsListScope);

  sender_.reset(new RequestSender(
      new google_apis::AuthService(oauth2_token_service_,
                                   account_id,
                                   url_request_context_getter_.get(),
                                   scopes),
      url_request_context_getter_.get(),
      blocking_task_runner_.get(),
      custom_user_agent_));
  sender_->auth_service()->AddObserver(this);
}

void DriveAPIService::AddObserver(DriveServiceObserver* observer) {
  observers_.AddObserver(observer);
}

void DriveAPIService::RemoveObserver(DriveServiceObserver* observer) {
  observers_.RemoveObserver(observer);
}

bool DriveAPIService::CanSendRequest() const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  return HasRefreshToken();
}

ResourceIdCanonicalizer DriveAPIService::GetResourceIdCanonicalizer() const {
  return base::Bind(&drive::util::CanonicalizeResourceId);
}

std::string DriveAPIService::GetRootResourceId() const {
  return kDriveApiRootDirectoryResourceId;
}

CancelCallback DriveAPIService::GetAllResourceList(
    const GetResourceListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesListRequest* request = new FilesListRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileListToResourceListOnBlockingPoolAndRun,
                 blocking_task_runner_, callback));
  request->set_max_results(kMaxNumFilesResourcePerRequest);
  request->set_q("trashed = false");  // Exclude trashed files.
  request->set_fields(kFileListFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::GetResourceListInDirectory(
    const std::string& directory_resource_id,
    const GetResourceListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!directory_resource_id.empty());
  DCHECK(!callback.is_null());

  // Because children.list method on Drive API v2 returns only the list of
  // children's references, but we need all file resource list.
  // So, here we use files.list method instead, with setting parents query.
  // After the migration from GData WAPI to Drive API v2, we should clean the
  // code up by moving the responsibility to include "parents" in the query
  // to client side.
  // We aren't interested in files in trash in this context, neither.
  FilesListRequest* request = new FilesListRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileListToResourceListOnBlockingPoolAndRun,
                 blocking_task_runner_, callback));
  request->set_max_results(kMaxNumFilesResourcePerRequest);
  request->set_q(base::StringPrintf(
      "'%s' in parents and trashed = false",
      drive::util::EscapeQueryStringValue(directory_resource_id).c_str()));
  request->set_fields(kFileListFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::Search(
    const std::string& search_query,
    const GetResourceListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!search_query.empty());
  DCHECK(!callback.is_null());

  FilesListRequest* request = new FilesListRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileListToResourceListOnBlockingPoolAndRun,
                 blocking_task_runner_, callback));
  request->set_max_results(kMaxNumFilesResourcePerRequestForSearch);
  request->set_q(drive::util::TranslateQuery(search_query));
  request->set_fields(kFileListFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::SearchByTitle(
    const std::string& title,
    const std::string& directory_resource_id,
    const GetResourceListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!title.empty());
  DCHECK(!callback.is_null());

  std::string query;
  base::StringAppendF(&query, "title = '%s'",
                      drive::util::EscapeQueryStringValue(title).c_str());
  if (!directory_resource_id.empty()) {
    base::StringAppendF(
        &query, " and '%s' in parents",
        drive::util::EscapeQueryStringValue(directory_resource_id).c_str());
  }
  query += " and trashed = false";

  FilesListRequest* request = new FilesListRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileListToResourceListOnBlockingPoolAndRun,
                 blocking_task_runner_, callback));
  request->set_max_results(kMaxNumFilesResourcePerRequest);
  request->set_q(query);
  request->set_fields(kFileListFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::GetChangeList(
    int64 start_changestamp,
    const GetResourceListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  ChangesListRequest* request = new ChangesListRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertChangeListToResourceListOnBlockingPoolAndRun,
                 blocking_task_runner_, callback));
  request->set_max_results(kMaxNumFilesResourcePerRequest);
  request->set_start_change_id(start_changestamp);
  request->set_fields(kChangeListFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::GetRemainingChangeList(
    const GURL& next_link,
    const GetResourceListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!next_link.is_empty());
  DCHECK(!callback.is_null());

  ChangesListNextPageRequest* request = new ChangesListNextPageRequest(
      sender_.get(),
      base::Bind(&ConvertChangeListToResourceListOnBlockingPoolAndRun,
                 blocking_task_runner_, callback));
  request->set_next_link(next_link);
  request->set_fields(kChangeListFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::GetRemainingFileList(
    const GURL& next_link,
    const GetResourceListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!next_link.is_empty());
  DCHECK(!callback.is_null());

  FilesListNextPageRequest* request = new FilesListNextPageRequest(
      sender_.get(),
      base::Bind(&ConvertFileListToResourceListOnBlockingPoolAndRun,
                 blocking_task_runner_, callback));
  request->set_next_link(next_link);
  request->set_fields(kFileListFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::GetResourceEntry(
    const std::string& resource_id,
    const GetResourceEntryCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesGetRequest* request = new FilesGetRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileEntryToResourceEntryAndRun, callback));
  request->set_file_id(resource_id);
  request->set_fields(kFileResourceFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::GetShareUrl(
    const std::string& resource_id,
    const GURL& embed_origin,
    const GetShareUrlCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  // Unfortunately "share url" is not yet supported on Drive API v2.
  // So, as a fallback, we use GData WAPI protocol for this method.
  // TODO(hidehiko): Get rid of this implementation when share url is
  // supported on Drive API v2.
  return sender_->StartRequestWithRetry(
      new GetResourceEntryRequest(sender_.get(),
                                  wapi_url_generator_,
                                  resource_id,
                                  embed_origin,
                                  base::Bind(&util::ParseShareUrlAndRun,
                                             callback)));
}

CancelCallback DriveAPIService::GetAboutResource(
    const AboutResourceCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  AboutGetRequest* request =
      new AboutGetRequest(sender_.get(), url_generator_, callback);
  request->set_fields(kAboutResourceFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::GetAppList(const AppListCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  return sender_->StartRequestWithRetry(
      new AppsListRequest(sender_.get(), url_generator_,
                          google_apis::IsGoogleChromeAPIKeyUsed(),
                          callback));
}

CancelCallback DriveAPIService::DownloadFile(
    const base::FilePath& local_cache_path,
    const std::string& resource_id,
    const DownloadActionCallback& download_action_callback,
    const GetContentCallback& get_content_callback,
    const ProgressCallback& progress_callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!download_action_callback.is_null());
  // get_content_callback may be null.

  return sender_->StartRequestWithRetry(
      new DownloadFileRequest(sender_.get(),
                              url_generator_,
                              resource_id,
                              local_cache_path,
                              download_action_callback,
                              get_content_callback,
                              progress_callback));
}

CancelCallback DriveAPIService::DeleteResource(
    const std::string& resource_id,
    const std::string& etag,
    const EntryActionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesDeleteRequest* request = new FilesDeleteRequest(
      sender_.get(), url_generator_, callback);
  request->set_file_id(resource_id);
  request->set_etag(etag);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::TrashResource(
    const std::string& resource_id,
    const EntryActionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesTrashRequest* request = new FilesTrashRequest(
      sender_.get(), url_generator_,
      base::Bind(&EntryActionCallbackAdapter, callback));
  request->set_file_id(resource_id);
  request->set_fields(kFileResourceFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::AddNewDirectory(
    const std::string& parent_resource_id,
    const std::string& directory_title,
    const AddNewDirectoryOptions& options,
    const GetResourceEntryCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesInsertRequest* request = new FilesInsertRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileEntryToResourceEntryAndRun, callback));
  request->set_last_viewed_by_me_date(options.last_viewed_by_me_date);
  request->set_mime_type(kFolderMimeType);
  request->set_modified_date(options.modified_date);
  request->add_parent(parent_resource_id);
  request->set_title(directory_title);
  request->set_fields(kFileResourceFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::CopyResource(
    const std::string& resource_id,
    const std::string& parent_resource_id,
    const std::string& new_title,
    const base::Time& last_modified,
    const GetResourceEntryCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesCopyRequest* request = new FilesCopyRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileEntryToResourceEntryAndRun, callback));
  request->set_file_id(resource_id);
  request->add_parent(parent_resource_id);
  request->set_title(new_title);
  request->set_modified_date(last_modified);
  request->set_fields(kFileResourceFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::UpdateResource(
    const std::string& resource_id,
    const std::string& parent_resource_id,
    const std::string& new_title,
    const base::Time& last_modified,
    const base::Time& last_viewed_by_me,
    const GetResourceEntryCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesPatchRequest* request = new FilesPatchRequest(
      sender_.get(), url_generator_,
      base::Bind(&ConvertFileEntryToResourceEntryAndRun, callback));
  request->set_file_id(resource_id);
  request->set_title(new_title);
  if (!parent_resource_id.empty())
    request->add_parent(parent_resource_id);
  if (!last_modified.is_null()) {
    // Need to set setModifiedDate to true to overwrite modifiedDate.
    request->set_set_modified_date(true);
    request->set_modified_date(last_modified);
  }
  if (!last_viewed_by_me.is_null()) {
    // Need to set updateViewedDate to false, otherwise the lastViewedByMeDate
    // will be set to the request time (not the specified time via request).
    request->set_update_viewed_date(false);
    request->set_last_viewed_by_me_date(last_viewed_by_me);
  }
  request->set_fields(kFileResourceFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::RenameResource(
    const std::string& resource_id,
    const std::string& new_title,
    const EntryActionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  FilesPatchRequest* request = new FilesPatchRequest(
      sender_.get(), url_generator_,
      base::Bind(&EntryActionCallbackAdapter, callback));
  request->set_file_id(resource_id);
  request->set_title(new_title);
  request->set_fields(kFileResourceFields);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::AddResourceToDirectory(
    const std::string& parent_resource_id,
    const std::string& resource_id,
    const EntryActionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  ChildrenInsertRequest* request =
      new ChildrenInsertRequest(sender_.get(), url_generator_, callback);
  request->set_folder_id(parent_resource_id);
  request->set_id(resource_id);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::RemoveResourceFromDirectory(
    const std::string& parent_resource_id,
    const std::string& resource_id,
    const EntryActionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  ChildrenDeleteRequest* request =
      new ChildrenDeleteRequest(sender_.get(), url_generator_, callback);
  request->set_child_id(resource_id);
  request->set_folder_id(parent_resource_id);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::InitiateUploadNewFile(
    const std::string& content_type,
    int64 content_length,
    const std::string& parent_resource_id,
    const std::string& title,
    const InitiateUploadNewFileOptions& options,
    const InitiateUploadCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  InitiateUploadNewFileRequest* request =
      new InitiateUploadNewFileRequest(sender_.get(),
                                       url_generator_,
                                       content_type,
                                       content_length,
                                       parent_resource_id,
                                       title,
                                       callback);
  request->set_modified_date(options.modified_date);
  request->set_last_viewed_by_me_date(options.last_viewed_by_me_date);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::InitiateUploadExistingFile(
    const std::string& content_type,
    int64 content_length,
    const std::string& resource_id,
    const InitiateUploadExistingFileOptions& options,
    const InitiateUploadCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  InitiateUploadExistingFileRequest* request =
      new InitiateUploadExistingFileRequest(sender_.get(),
                                            url_generator_,
                                            content_type,
                                            content_length,
                                            resource_id,
                                            options.etag,
                                            callback);
  request->set_parent_resource_id(options.parent_resource_id);
  request->set_title(options.title);
  request->set_modified_date(options.modified_date);
  request->set_last_viewed_by_me_date(options.last_viewed_by_me_date);
  return sender_->StartRequestWithRetry(request);
}

CancelCallback DriveAPIService::ResumeUpload(
    const GURL& upload_url,
    int64 start_position,
    int64 end_position,
    int64 content_length,
    const std::string& content_type,
    const base::FilePath& local_file_path,
    const UploadRangeCallback& callback,
    const ProgressCallback& progress_callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  return sender_->StartRequestWithRetry(
      new ResumeUploadRequest(
          sender_.get(),
          upload_url,
          start_position,
          end_position,
          content_length,
          content_type,
          local_file_path,
          base::Bind(&ConvertFileResourceToResourceEntryForUploadRangeAndRun,
                     callback),
          progress_callback));
}

CancelCallback DriveAPIService::GetUploadStatus(
    const GURL& upload_url,
    int64 content_length,
    const UploadRangeCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  return sender_->StartRequestWithRetry(new GetUploadStatusRequest(
      sender_.get(),
      upload_url,
      content_length,
      base::Bind(&ConvertFileResourceToResourceEntryForUploadRangeAndRun,
                 callback)));
}

CancelCallback DriveAPIService::AuthorizeApp(
    const std::string& resource_id,
    const std::string& app_id,
    const AuthorizeAppCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  // Files.Authorize is only available for whitelisted clients like official
  // Google Chrome. In other cases, we fall back to Files.Get that returns the
  // same value as Files.Authorize without doing authorization. In that case,
  // the app can open if it was authorized by other means (from whitelisted
  // clients or drive.google.com web UI.)
  if (google_apis::IsGoogleChromeAPIKeyUsed()) {
    google_apis::drive::FilesAuthorizeRequest* request =
        new google_apis::drive::FilesAuthorizeRequest(
            sender_.get(), url_generator_,
            base::Bind(&ExtractOpenUrlAndRun, app_id, callback));
    request->set_app_id(app_id);
    request->set_file_id(resource_id);
    request->set_fields(kFileResourceOpenWithLinksFields);
    return sender_->StartRequestWithRetry(request);
  } else {
    FilesGetRequest* request = new FilesGetRequest(
        sender_.get(), url_generator_,
        base::Bind(&ExtractOpenUrlAndRun, app_id, callback));
    request->set_file_id(resource_id);
    request->set_fields(kFileResourceOpenWithLinksFields);
    return sender_->StartRequestWithRetry(request);
  }
}

CancelCallback DriveAPIService::UninstallApp(
    const std::string& app_id,
    const google_apis::EntryActionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  google_apis::drive::AppsDeleteRequest* request =
      new google_apis::drive::AppsDeleteRequest(sender_.get(), url_generator_,
                                                callback);
  request->set_app_id(app_id);
  return sender_->StartRequestWithRetry(request);
}

google_apis::CancelCallback DriveAPIService::AddPermission(
    const std::string& resource_id,
    const std::string& email,
    google_apis::drive::PermissionRole role,
    const google_apis::EntryActionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  google_apis::drive::PermissionsInsertRequest* request =
      new google_apis::drive::PermissionsInsertRequest(sender_.get(),
                                                       url_generator_,
                                                       callback);
  request->set_id(resource_id);
  request->set_role(role);
  request->set_type(google_apis::drive::PERMISSION_TYPE_USER);
  request->set_value(email);
  return sender_->StartRequestWithRetry(request);
}

bool DriveAPIService::HasAccessToken() const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  return sender_->auth_service()->HasAccessToken();
}

void DriveAPIService::RequestAccessToken(const AuthStatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  const std::string access_token = sender_->auth_service()->access_token();
  if (!access_token.empty()) {
    callback.Run(google_apis::HTTP_NOT_MODIFIED, access_token);
    return;
  }

  // Retrieve the new auth token.
  sender_->auth_service()->StartAuthentication(callback);
}

bool DriveAPIService::HasRefreshToken() const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  return sender_->auth_service()->HasRefreshToken();
}

void DriveAPIService::ClearAccessToken() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  sender_->auth_service()->ClearAccessToken();
}

void DriveAPIService::ClearRefreshToken() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  sender_->auth_service()->ClearRefreshToken();
}

void DriveAPIService::OnOAuth2RefreshTokenChanged() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (CanSendRequest()) {
    FOR_EACH_OBSERVER(
        DriveServiceObserver, observers_, OnReadyToSendRequests());
  } else if (!HasRefreshToken()) {
    FOR_EACH_OBSERVER(
        DriveServiceObserver, observers_, OnRefreshTokenInvalid());
  }
}

}  // namespace drive

/* [<][>][^][v][top][bottom][index][help] */