text: // 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/google_apis/gdata_wapi_requests.h"
  
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/stringprintf.h"
  #include "base/task_runner_util.h"
  #include "base/threading/sequenced_worker_pool.h"
  #include "base/values.h"
  #include "chrome/browser/google_apis/gdata_wapi_parser.h"
  #include "chrome/browser/google_apis/gdata_wapi_url_generator.h"
  #include "chrome/browser/google_apis/request_util.h"
  #include "chrome/browser/google_apis/time_util.h"
  #include "content/public/browser/browser_thread.h"
  #include "net/base/escape.h"
  #include "net/base/url_util.h"
  #include "third_party/libxml/chromium/libxml_utils.h"
  
  using content::BrowserThread;
  using net::URLFetcher;
  
  namespace google_apis {
  
  namespace {
  
  
  const char kUploadContentRange[] = "Content-Range: bytes ";
  
  const char kFeedField[] = "feed";
  
  // Templates for file uploading.
  const char kUploadResponseRange[] = "range";
  
  // Parses the JSON value to ResourceList.
  scoped_ptr<ResourceList> ParseResourceListOnBlockingPool(
      scoped_ptr<base::Value> value) {
    DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
    DCHECK(value);
  
    return ResourceList::ExtractAndParse(*value);
  }
  
  // Runs |callback| with |error| and |value|, but replace the error code with
  // GDATA_PARSE_ERROR, if there was a parsing error.
  void DidParseResourceListOnBlockingPool(
      const GetResourceListCallback& callback,
      GDataErrorCode error,
      scoped_ptr<ResourceList> resource_list) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    DCHECK(!callback.is_null());
  
    // resource_list being NULL indicates there was a parsing error.
    if (!resource_list)
      error = GDATA_PARSE_ERROR;
  
    callback.Run(error, resource_list.Pass());
  }
  
  // Parses the JSON value to ResourceList on the blocking pool and runs
  // |callback| on the UI thread once parsing is done.
  void ParseResourceListAndRun(const GetResourceListCallback& callback,
                               GDataErrorCode error,
                               scoped_ptr<base::Value> value) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    DCHECK(!callback.is_null());
  
    if (!value) {
      callback.Run(error, scoped_ptr<ResourceList>());
      return;
    }
  
    base::PostTaskAndReplyWithResult(
        BrowserThread::GetBlockingPool(),
        FROM_HERE,
        base::Bind(&ParseResourceListOnBlockingPool, base::Passed(&value)),
        base::Bind(&DidParseResourceListOnBlockingPool, callback, error));
  }
  
  // Parses the JSON value to AccountMetadata and runs |callback| on the UI
  // thread once parsing is done.
  void ParseAccounetMetadataAndRun(const GetAccountMetadataCallback& callback,
                                   GDataErrorCode error,
                                   scoped_ptr<base::Value> value) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    DCHECK(!callback.is_null());
  
    if (!value) {
      callback.Run(error, scoped_ptr<AccountMetadata>());
      return;
    }
  
    // Parsing AccountMetadata is cheap enough to do on UI thread.
    scoped_ptr<AccountMetadata> entry =
        google_apis::AccountMetadata::CreateFrom(*value);
    if (!entry) {
      callback.Run(GDATA_PARSE_ERROR, scoped_ptr<AccountMetadata>());
      return;
    }
  
    callback.Run(error, entry.Pass());
  }
  
  // Parses the |value| to ResourceEntry with error handling.
  // This is designed to be used for ResumeUploadRequest and
  // GetUploadStatusRequest.
  scoped_ptr<ResourceEntry> ParseResourceEntry(scoped_ptr<base::Value> value) {
    scoped_ptr<ResourceEntry> entry;
    if (value.get()) {
      entry = ResourceEntry::ExtractAndParse(*value);
  
      // Note: |value| may be NULL, in particular if the callback is for a
      // failure.
      if (!entry.get())
        LOG(WARNING) << "Invalid entry received on upload.";
    }
  
    return entry.Pass();
  }
  
  // Extracts the open link url from the JSON Feed. Used by AuthorizeApp().
  void ParseOpenLinkAndRun(const std::string& app_id,
                           const AuthorizeAppCallback& callback,
                           GDataErrorCode error,
                           scoped_ptr<base::Value> value) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    DCHECK(!callback.is_null());
  
    if (!value) {
      callback.Run(error, GURL());
      return;
    }
  
    // Parsing ResourceEntry is cheap enough to do on UI thread.
    scoped_ptr<ResourceEntry> resource_entry = ParseResourceEntry(value.Pass());
    if (!resource_entry) {
      callback.Run(GDATA_PARSE_ERROR, GURL());
      return;
    }
  
    // Look for the link to open the file with the app with |app_id|.
    const ScopedVector<Link>& resource_links = resource_entry->links();
    GURL open_link;
    for (size_t i = 0; i < resource_links.size(); ++i) {
      const Link& link = *resource_links[i];
      if (link.type() == Link::LINK_OPEN_WITH && link.app_id() == app_id) {
        open_link = link.href();
        break;
      }
    }
  
    if (open_link.is_empty())
      error = GDATA_OTHER_ERROR;
  
    callback.Run(error, open_link);
  }
  
  }  // namespace
  
  //============================ GetResourceListRequest ========================
  
  GetResourceListRequest::GetResourceListRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const GURL& override_url,
      int64 start_changestamp,
      const std::string& search_string,
      const std::string& directory_resource_id,
      const GetResourceListCallback& callback)
      : GetDataRequest(runner, url_request_context_getter,
                       base::Bind(&ParseResourceListAndRun, callback)),
        url_generator_(url_generator),
        override_url_(override_url),
        start_changestamp_(start_changestamp),
        search_string_(search_string),
        directory_resource_id_(directory_resource_id) {
    DCHECK(!callback.is_null());
  }
  
  GetResourceListRequest::~GetResourceListRequest() {}
  
  GURL GetResourceListRequest::GetURL() const {
    return url_generator_.GenerateResourceListUrl(override_url_,
                                                  start_changestamp_,
                                                  search_string_,
                                                  directory_resource_id_);
  }
  
  //============================ SearchByTitleRequest ==========================
  
  SearchByTitleRequest::SearchByTitleRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const std::string& title,
      const std::string& directory_resource_id,
      const GetResourceListCallback& callback)
      : GetDataRequest(runner, url_request_context_getter,
                       base::Bind(&ParseResourceListAndRun, callback)),
        url_generator_(url_generator),
        title_(title),
        directory_resource_id_(directory_resource_id) {
    DCHECK(!callback.is_null());
  }
  
  SearchByTitleRequest::~SearchByTitleRequest() {}
  
  GURL SearchByTitleRequest::GetURL() const {
    return url_generator_.GenerateSearchByTitleUrl(
        title_, directory_resource_id_);
  }
  
  //============================ GetResourceEntryRequest =======================
  
  GetResourceEntryRequest::GetResourceEntryRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const std::string& resource_id,
      const GetDataCallback& callback)
      : GetDataRequest(runner, url_request_context_getter, callback),
        url_generator_(url_generator),
        resource_id_(resource_id) {
    DCHECK(!callback.is_null());
  }
  
  GetResourceEntryRequest::~GetResourceEntryRequest() {}
  
  GURL GetResourceEntryRequest::GetURL() const {
    return url_generator_.GenerateEditUrl(resource_id_);
  }
  
  //========================= GetAccountMetadataRequest ========================
  
  GetAccountMetadataRequest::GetAccountMetadataRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const GetAccountMetadataCallback& callback,
      bool include_installed_apps)
      : GetDataRequest(runner, url_request_context_getter,
                       base::Bind(&ParseAccounetMetadataAndRun, callback)),
        url_generator_(url_generator),
        include_installed_apps_(include_installed_apps) {
    DCHECK(!callback.is_null());
  }
  
  GetAccountMetadataRequest::~GetAccountMetadataRequest() {}
  
  GURL GetAccountMetadataRequest::GetURL() const {
    return url_generator_.GenerateAccountMetadataUrl(include_installed_apps_);
  }
  
  //=========================== DeleteResourceRequest ==========================
  
  DeleteResourceRequest::DeleteResourceRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const EntryActionCallback& callback,
      const std::string& resource_id,
      const std::string& etag)
      : EntryActionRequest(runner, url_request_context_getter, callback),
        url_generator_(url_generator),
        resource_id_(resource_id),
        etag_(etag) {
    DCHECK(!callback.is_null());
  }
  
  DeleteResourceRequest::~DeleteResourceRequest() {}
  
  GURL DeleteResourceRequest::GetURL() const {
    return url_generator_.GenerateEditUrl(resource_id_);
  }
  
  URLFetcher::RequestType DeleteResourceRequest::GetRequestType() const {
    return URLFetcher::DELETE_REQUEST;
  }
  
  std::vector<std::string>
  DeleteResourceRequest::GetExtraRequestHeaders() const {
    std::vector<std::string> headers;
    headers.push_back(util::GenerateIfMatchHeader(etag_));
    return headers;
  }
  
  //========================== CreateDirectoryRequest ==========================
  
  CreateDirectoryRequest::CreateDirectoryRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const GetDataCallback& callback,
      const std::string& parent_resource_id,
      const std::string& directory_name)
      : GetDataRequest(runner, url_request_context_getter, callback),
        url_generator_(url_generator),
        parent_resource_id_(parent_resource_id),
        directory_name_(directory_name) {
    DCHECK(!callback.is_null());
  }
  
  CreateDirectoryRequest::~CreateDirectoryRequest() {}
  
  GURL CreateDirectoryRequest::GetURL() const {
    return url_generator_.GenerateContentUrl(parent_resource_id_);
  }
  
  URLFetcher::RequestType
  CreateDirectoryRequest::GetRequestType() const {
    return URLFetcher::POST;
  }
  
  bool CreateDirectoryRequest::GetContentData(std::string* upload_content_type,
                                              std::string* upload_content) {
    upload_content_type->assign("application/atom+xml");
    XmlWriter xml_writer;
    xml_writer.StartWriting();
    xml_writer.StartElement("entry");
    xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
  
    xml_writer.StartElement("category");
    xml_writer.AddAttribute("scheme",
                            "http://schemas.google.com/g/2005#kind");
    xml_writer.AddAttribute("term",
                            "http://schemas.google.com/docs/2007#folder");
    xml_writer.EndElement();  // Ends "category" element.
  
    xml_writer.WriteElement("title", directory_name_);
  
    xml_writer.EndElement();  // Ends "entry" element.
    xml_writer.StopWriting();
    upload_content->assign(xml_writer.GetWrittenString());
    DVLOG(1) << "CreateDirectory data: " << *upload_content_type << ", ["
             << *upload_content << "]";
    return true;
  }
  
  //============================ CopyHostedDocumentRequest =====================
  
  CopyHostedDocumentRequest::CopyHostedDocumentRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const GetDataCallback& callback,
      const std::string& resource_id,
      const std::string& new_name)
      : GetDataRequest(runner, url_request_context_getter, callback),
        url_generator_(url_generator),
        resource_id_(resource_id),
        new_name_(new_name) {
    DCHECK(!callback.is_null());
  }
  
  CopyHostedDocumentRequest::~CopyHostedDocumentRequest() {}
  
  URLFetcher::RequestType CopyHostedDocumentRequest::GetRequestType() const {
    return URLFetcher::POST;
  }
  
  GURL CopyHostedDocumentRequest::GetURL() const {
    return url_generator_.GenerateResourceListRootUrl();
  }
  
  bool CopyHostedDocumentRequest::GetContentData(
      std::string* upload_content_type,
      std::string* upload_content) {
    upload_content_type->assign("application/atom+xml");
    XmlWriter xml_writer;
    xml_writer.StartWriting();
    xml_writer.StartElement("entry");
    xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
  
    xml_writer.WriteElement("id", resource_id_);
    xml_writer.WriteElement("title", new_name_);
  
    xml_writer.EndElement();  // Ends "entry" element.
    xml_writer.StopWriting();
    upload_content->assign(xml_writer.GetWrittenString());
    DVLOG(1) << "CopyHostedDocumentRequest data: " << *upload_content_type
             << ", [" << *upload_content << "]";
    return true;
  }
  
  //=========================== RenameResourceRequest ==========================
  
  RenameResourceRequest::RenameResourceRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const EntryActionCallback& callback,
      const std::string& resource_id,
      const std::string& new_name)
      : EntryActionRequest(runner, url_request_context_getter, callback),
        url_generator_(url_generator),
        resource_id_(resource_id),
        new_name_(new_name) {
    DCHECK(!callback.is_null());
  }
  
  RenameResourceRequest::~RenameResourceRequest() {}
  
  URLFetcher::RequestType RenameResourceRequest::GetRequestType() const {
    return URLFetcher::PUT;
  }
  
  std::vector<std::string>
  RenameResourceRequest::GetExtraRequestHeaders() const {
    std::vector<std::string> headers;
    headers.push_back(util::kIfMatchAllHeader);
    return headers;
  }
  
  GURL RenameResourceRequest::GetURL() const {
    return url_generator_.GenerateEditUrl(resource_id_);
  }
  
  bool RenameResourceRequest::GetContentData(std::string* upload_content_type,
                                             std::string* upload_content) {
    upload_content_type->assign("application/atom+xml");
    XmlWriter xml_writer;
    xml_writer.StartWriting();
    xml_writer.StartElement("entry");
    xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
  
    xml_writer.WriteElement("title", new_name_);
  
    xml_writer.EndElement();  // Ends "entry" element.
    xml_writer.StopWriting();
    upload_content->assign(xml_writer.GetWrittenString());
    DVLOG(1) << "RenameResourceRequest data: " << *upload_content_type << ", ["
             << *upload_content << "]";
    return true;
  }
  
  //=========================== AuthorizeAppRequest ==========================
  
  AuthorizeAppRequest::AuthorizeAppRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const AuthorizeAppCallback& callback,
      const std::string& resource_id,
      const std::string& app_id)
      : GetDataRequest(runner, url_request_context_getter,
                       base::Bind(&ParseOpenLinkAndRun, app_id, callback)),
        url_generator_(url_generator),
        resource_id_(resource_id),
        app_id_(app_id) {
    DCHECK(!callback.is_null());
  }
  
  AuthorizeAppRequest::~AuthorizeAppRequest() {}
  
  URLFetcher::RequestType AuthorizeAppRequest::GetRequestType() const {
    return URLFetcher::PUT;
  }
  
  std::vector<std::string>
  AuthorizeAppRequest::GetExtraRequestHeaders() const {
    std::vector<std::string> headers;
    headers.push_back(util::kIfMatchAllHeader);
    return headers;
  }
  
  bool AuthorizeAppRequest::GetContentData(std::string* upload_content_type,
                                           std::string* upload_content) {
    upload_content_type->assign("application/atom+xml");
    XmlWriter xml_writer;
    xml_writer.StartWriting();
    xml_writer.StartElement("entry");
    xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
    xml_writer.AddAttribute("xmlns:docs", "http://schemas.google.com/docs/2007");
    xml_writer.WriteElement("docs:authorizedApp", app_id_);
  
    xml_writer.EndElement();  // Ends "entry" element.
    xml_writer.StopWriting();
    upload_content->assign(xml_writer.GetWrittenString());
    DVLOG(1) << "AuthorizeAppRequest data: " << *upload_content_type << ", ["
             << *upload_content << "]";
    return true;
  }
  
  GURL AuthorizeAppRequest::GetURL() const {
    return url_generator_.GenerateEditUrl(resource_id_);
  }
  
  //======================= AddResourceToDirectoryRequest ======================
  
  AddResourceToDirectoryRequest::AddResourceToDirectoryRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const EntryActionCallback& callback,
      const std::string& parent_resource_id,
      const std::string& resource_id)
      : EntryActionRequest(runner, url_request_context_getter, callback),
        url_generator_(url_generator),
        parent_resource_id_(parent_resource_id),
        resource_id_(resource_id) {
    DCHECK(!callback.is_null());
  }
  
  AddResourceToDirectoryRequest::~AddResourceToDirectoryRequest() {}
  
  GURL AddResourceToDirectoryRequest::GetURL() const {
    return url_generator_.GenerateContentUrl(parent_resource_id_);
  }
  
  URLFetcher::RequestType
  AddResourceToDirectoryRequest::GetRequestType() const {
    return URLFetcher::POST;
  }
  
  bool AddResourceToDirectoryRequest::GetContentData(
      std::string* upload_content_type, std::string* upload_content) {
    upload_content_type->assign("application/atom+xml");
    XmlWriter xml_writer;
    xml_writer.StartWriting();
    xml_writer.StartElement("entry");
    xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
  
    xml_writer.WriteElement(
        "id", url_generator_.GenerateEditUrlWithoutParams(resource_id_).spec());
  
    xml_writer.EndElement();  // Ends "entry" element.
    xml_writer.StopWriting();
    upload_content->assign(xml_writer.GetWrittenString());
    DVLOG(1) << "AddResourceToDirectoryRequest data: " << *upload_content_type
             << ", [" << *upload_content << "]";
    return true;
  }
  
  //==================== RemoveResourceFromDirectoryRequest ====================
  
  RemoveResourceFromDirectoryRequest::RemoveResourceFromDirectoryRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const EntryActionCallback& callback,
      const std::string& parent_resource_id,
      const std::string& document_resource_id)
      : EntryActionRequest(runner, url_request_context_getter, callback),
        url_generator_(url_generator),
        resource_id_(document_resource_id),
        parent_resource_id_(parent_resource_id) {
    DCHECK(!callback.is_null());
  }
  
  RemoveResourceFromDirectoryRequest::~RemoveResourceFromDirectoryRequest() {
  }
  
  GURL RemoveResourceFromDirectoryRequest::GetURL() const {
    return url_generator_.GenerateResourceUrlForRemoval(
        parent_resource_id_, resource_id_);
  }
  
  URLFetcher::RequestType
  RemoveResourceFromDirectoryRequest::GetRequestType() const {
    return URLFetcher::DELETE_REQUEST;
  }
  
  std::vector<std::string>
  RemoveResourceFromDirectoryRequest::GetExtraRequestHeaders() const {
    std::vector<std::string> headers;
    headers.push_back(util::kIfMatchAllHeader);
    return headers;
  }
  
  //======================= InitiateUploadNewFileRequest =======================
  
  InitiateUploadNewFileRequest::InitiateUploadNewFileRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const InitiateUploadCallback& callback,
      const base::FilePath& drive_file_path,
      const std::string& content_type,
      int64 content_length,
      const std::string& parent_resource_id,
      const std::string& title)
      : InitiateUploadRequestBase(runner,
                                  url_request_context_getter,
                                  callback,
                                  drive_file_path,
                                  content_type,
                                  content_length),
        url_generator_(url_generator),
        parent_resource_id_(parent_resource_id),
        title_(title) {
  }
  
  InitiateUploadNewFileRequest::~InitiateUploadNewFileRequest() {}
  
  GURL InitiateUploadNewFileRequest::GetURL() const {
    return url_generator_.GenerateInitiateUploadNewFileUrl(parent_resource_id_);
  }
  
  net::URLFetcher::RequestType
  InitiateUploadNewFileRequest::GetRequestType() const {
    return net::URLFetcher::POST;
  }
  
  bool InitiateUploadNewFileRequest::GetContentData(
      std::string* upload_content_type,
      std::string* upload_content) {
    upload_content_type->assign("application/atom+xml");
    XmlWriter xml_writer;
    xml_writer.StartWriting();
    xml_writer.StartElement("entry");
    xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom");
    xml_writer.AddAttribute("xmlns:docs",
                            "http://schemas.google.com/docs/2007");
    xml_writer.WriteElement("title", title_);
    xml_writer.EndElement();  // Ends "entry" element.
    xml_writer.StopWriting();
    upload_content->assign(xml_writer.GetWrittenString());
    DVLOG(1) << "InitiateUploadNewFile: " << *upload_content_type << ", ["
             << *upload_content << "]";
    return true;
  }
  
  //===================== InitiateUploadExistingFileRequest ====================
  
  InitiateUploadExistingFileRequest::InitiateUploadExistingFileRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const GDataWapiUrlGenerator& url_generator,
      const InitiateUploadCallback& callback,
      const base::FilePath& drive_file_path,
      const std::string& content_type,
      int64 content_length,
      const std::string& resource_id,
      const std::string& etag)
      : InitiateUploadRequestBase(runner,
                                  url_request_context_getter,
                                  callback,
                                  drive_file_path,
                                  content_type,
                                  content_length),
        url_generator_(url_generator),
        resource_id_(resource_id),
        etag_(etag) {
  }
  
  InitiateUploadExistingFileRequest::~InitiateUploadExistingFileRequest() {}
  
  GURL InitiateUploadExistingFileRequest::GetURL() const {
    return url_generator_.GenerateInitiateUploadExistingFileUrl(resource_id_);
  }
  
  net::URLFetcher::RequestType
  InitiateUploadExistingFileRequest::GetRequestType() const {
    return net::URLFetcher::PUT;
  }
  
  bool InitiateUploadExistingFileRequest::GetContentData(
      std::string* upload_content_type,
      std::string* upload_content) {
    // According to the document there is no need to send the content-type.
    // However, the server would return 500 server error without the
    // content-type.
    // As its workaround, send "text/plain" content-type here.
    *upload_content_type = "text/plain";
    *upload_content = "";
    return true;
  }
  
  std::vector<std::string>
  InitiateUploadExistingFileRequest::GetExtraRequestHeaders() const {
    std::vector<std::string> headers(
        InitiateUploadRequestBase::GetExtraRequestHeaders());
    headers.push_back(util::GenerateIfMatchHeader(etag_));
    return headers;
  }
  
  //============================ ResumeUploadRequest ===========================
  
  ResumeUploadRequest::ResumeUploadRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const UploadRangeCallback& callback,
      const ProgressCallback& progress_callback,
      const base::FilePath& drive_file_path,
      const GURL& upload_location,
      int64 start_position,
      int64 end_position,
      int64 content_length,
      const std::string& content_type,
      const base::FilePath& local_file_path)
      : ResumeUploadRequestBase(runner,
                                url_request_context_getter,
                                drive_file_path,
                                upload_location,
                                start_position,
                                end_position,
                                content_length,
                                content_type,
                                local_file_path),
        callback_(callback),
        progress_callback_(progress_callback) {
    DCHECK(!callback_.is_null());
  }
  
  ResumeUploadRequest::~ResumeUploadRequest() {}
  
  void ResumeUploadRequest::OnRangeRequestComplete(
      const UploadRangeResponse& response, scoped_ptr<base::Value> value) {
    callback_.Run(response, ParseResourceEntry(value.Pass()));
  }
  
  void ResumeUploadRequest::OnURLFetchUploadProgress(
      const URLFetcher* source, int64 current, int64 total) {
    if (!progress_callback_.is_null())
      progress_callback_.Run(current, total);
  }
  
  //========================== GetUploadStatusRequest ==========================
  
  GetUploadStatusRequest::GetUploadStatusRequest(
      RequestSender* runner,
      net::URLRequestContextGetter* url_request_context_getter,
      const UploadRangeCallback& callback,
      const base::FilePath& drive_file_path,
      const GURL& upload_url,
      int64 content_length)
      : GetUploadStatusRequestBase(runner,
                                   url_request_context_getter,
                                   drive_file_path,
                                   upload_url,
                                   content_length),
        callback_(callback) {
    DCHECK(!callback.is_null());
  }
  
  GetUploadStatusRequest::~GetUploadStatusRequest() {}
  
  void GetUploadStatusRequest::OnRangeRequestComplete(
      const UploadRangeResponse& response, scoped_ptr<base::Value> value) {
    callback_.Run(response, ParseResourceEntry(value.Pass()));
  }
  
  }  // namespace google_apis
  