blob: 046671d9f4d0ca177101c14a72eb53ea4398c60b [file] [log] [blame]
// 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_operations.h"
#include "base/string_number_conversions.h"
#include "base/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/google_apis/gdata_util.h"
#include "chrome/browser/google_apis/gdata_wapi_parser.h"
#include "chrome/common/net/url_util.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/escape.h"
#include "net/http/http_util.h"
#include "third_party/libxml/chromium/libxml_utils.h"
using net::URLFetcher;
namespace {
// etag matching header.
const char kIfMatchAllHeader[] = "If-Match: *";
const char kIfMatchHeaderFormat[] = "If-Match: %s";
// URL requesting documents list that belong to the authenticated user only
// (handled with '/-/mine' part).
const char kGetDocumentListURLForAllDocuments[] =
"https://docs.google.com/feeds/default/private/full/-/mine";
// URL requesting documents list in a particular directory specified by "%s"
// that belong to the authenticated user only (handled with '/-/mine' part).
const char kGetDocumentListURLForDirectoryFormat[] =
"https://docs.google.com/feeds/default/private/full/%s/contents/-/mine";
// URL requesting documents list of changes to documents collections.
const char kGetChangesListURL[] =
"https://docs.google.com/feeds/default/private/changes";
// Root document list url.
const char kDocumentListRootURL[] =
"https://docs.google.com/feeds/default/private/full";
// URL requesting single document entry whose resource id is specified by "%s".
const char kGetDocumentEntryURLFormat[] =
"https://docs.google.com/feeds/default/private/full/%s";
// Metadata feed with things like user quota.
const char kAccountMetadataURL[] =
"https://docs.google.com/feeds/metadata/default";
// URL requesting all contact groups.
const char kGetContactGroupsURL[] =
"https://www.google.com/m8/feeds/groups/default/full?alt=json";
// URL requesting all contacts.
// TODO(derat): Per https://goo.gl/AufHP, "The feed may not contain all of the
// user's contacts, because there's a default limit on the number of results
// returned." Decide if 10000 is reasonable or not.
const char kGetContactsURL[] =
"https://www.google.com/m8/feeds/contacts/default/full"
"?alt=json&showdeleted=true&max-results=10000";
// Query parameter optionally appended to |kGetContactsURL| to return contacts
// from a specific group (as opposed to all contacts).
const char kGetContactsGroupParam[] = "group";
// Query parameter optionally appended to |kGetContactsURL| to return only
// recently-updated contacts.
const char kGetContactsUpdatedMinParam[] = "updated-min";
const char kUploadContentRange[] = "Content-Range: bytes ";
const char kUploadContentType[] = "X-Upload-Content-Type: ";
const char kUploadContentLength[] = "X-Upload-Content-Length: ";
#ifndef NDEBUG
// Use smaller 'page' size while debugging to ensure we hit feed reload
// almost always. Be careful not to use something too small on account that
// have many items because server side 503 error might kick in.
const int kMaxDocumentsPerFeed = 500;
const int kMaxDocumentsPerSearchFeed = 50;
#else
const int kMaxDocumentsPerFeed = 500;
const int kMaxDocumentsPerSearchFeed = 50;
#endif
const char kFeedField[] = "feed";
// Templates for file uploading.
const char kUploadParamConvertKey[] = "convert";
const char kUploadParamConvertValue[] = "false";
const char kUploadResponseLocation[] = "location";
const char kUploadResponseRange[] = "range";
// Adds additional parameters for API version, output content type and to show
// folders in the feed are added to document feed URLs.
GURL AddStandardUrlParams(const GURL& url) {
GURL result =
chrome_common_net::AppendOrReplaceQueryParameter(url, "v", "3");
result =
chrome_common_net::AppendOrReplaceQueryParameter(result, "alt", "json");
return result;
}
// Adds additional parameters to metadata feed to include installed 3rd party
// applications.
GURL AddMetadataUrlParams(const GURL& url) {
GURL result = AddStandardUrlParams(url);
result = chrome_common_net::AppendOrReplaceQueryParameter(
result, "include-installed-apps", "true");
return result;
}
// Adds additional parameters for API version, output content type and to show
// folders in the feed are added to document feed URLs.
GURL AddFeedUrlParams(const GURL& url,
int num_items_to_fetch,
int changestamp,
const std::string& search_string) {
GURL result = AddStandardUrlParams(url);
result = chrome_common_net::AppendOrReplaceQueryParameter(
result,
"showfolders",
"true");
result = chrome_common_net::AppendOrReplaceQueryParameter(
result,
"max-results",
base::StringPrintf("%d", num_items_to_fetch));
result = chrome_common_net::AppendOrReplaceQueryParameter(
result, "include-installed-apps", "true");
if (changestamp) {
result = chrome_common_net::AppendQueryParameter(
result,
"start-index",
base::StringPrintf("%d", changestamp));
}
if (!search_string.empty()) {
result = chrome_common_net::AppendOrReplaceQueryParameter(
result, "q", search_string);
}
return result;
}
// Formats a URL for getting document list. If |directory_resource_id| is
// empty, returns a URL for fetching all documents. If it's given, returns a
// URL for fetching documents in a particular directory.
GURL FormatDocumentListURL(const std::string& directory_resource_id) {
if (directory_resource_id.empty())
return GURL(kGetDocumentListURLForAllDocuments);
return GURL(base::StringPrintf(kGetDocumentListURLForDirectoryFormat,
net::EscapePath(
directory_resource_id).c_str()));
}
} // namespace
namespace gdata {
//============================ Structs ===========================
ResumeUploadResponse::ResumeUploadResponse(GDataErrorCode code,
int64 start_range_received,
int64 end_range_received)
: code(code),
start_range_received(start_range_received),
end_range_received(end_range_received) {
}
ResumeUploadResponse::~ResumeUploadResponse() {
}
InitiateUploadParams::InitiateUploadParams(
UploadMode upload_mode,
const std::string& title,
const std::string& content_type,
int64 content_length,
const GURL& upload_location,
const FilePath& virtual_path)
: upload_mode(upload_mode),
title(title),
content_type(content_type),
content_length(content_length),
upload_location(upload_location),
virtual_path(virtual_path) {
}
InitiateUploadParams::~InitiateUploadParams() {
}
ResumeUploadParams::ResumeUploadParams(
UploadMode upload_mode,
int64 start_range,
int64 end_range,
int64 content_length,
const std::string& content_type,
scoped_refptr<net::IOBuffer> buf,
const GURL& upload_location,
const FilePath& virtual_path) : upload_mode(upload_mode),
start_range(start_range),
end_range(end_range),
content_length(content_length),
content_type(content_type),
buf(buf),
upload_location(upload_location),
virtual_path(virtual_path) {
}
ResumeUploadParams::~ResumeUploadParams() {
}
//============================ GetDocumentsOperation ===========================
GetDocumentsOperation::GetDocumentsOperation(
OperationRegistry* registry,
const GURL& url,
int start_changestamp,
const std::string& search_string,
const std::string& directory_resource_id,
const GetDataCallback& callback)
: GetDataOperation(registry, callback),
override_url_(url),
start_changestamp_(start_changestamp),
search_string_(search_string),
directory_resource_id_(directory_resource_id) {
}
GetDocumentsOperation::~GetDocumentsOperation() {}
GURL GetDocumentsOperation::GetURL() const {
int max_docs = search_string_.empty() ? kMaxDocumentsPerFeed :
kMaxDocumentsPerSearchFeed;
if (!override_url_.is_empty())
return AddFeedUrlParams(override_url_,
max_docs,
0,
search_string_);
if (start_changestamp_ == 0) {
return AddFeedUrlParams(FormatDocumentListURL(directory_resource_id_),
max_docs,
0,
search_string_);
}
return AddFeedUrlParams(GURL(kGetChangesListURL),
kMaxDocumentsPerFeed,
start_changestamp_,
std::string());
}
//============================ GetDocumentEntryOperation =======================
GetDocumentEntryOperation::GetDocumentEntryOperation(
OperationRegistry* registry,
const std::string& resource_id,
const GetDataCallback& callback)
: GetDataOperation(registry, callback),
resource_id_(resource_id) {
}
GetDocumentEntryOperation::~GetDocumentEntryOperation() {}
GURL GetDocumentEntryOperation::GetURL() const {
GURL result = GURL(base::StringPrintf(kGetDocumentEntryURLFormat,
net::EscapePath(resource_id_).c_str()));
return AddStandardUrlParams(result);
}
//========================= GetAccountMetadataOperation ========================
GetAccountMetadataOperation::GetAccountMetadataOperation(
OperationRegistry* registry,
const GetDataCallback& callback)
: GetDataOperation(registry, callback) {
}
GetAccountMetadataOperation::~GetAccountMetadataOperation() {}
GURL GetAccountMetadataOperation::GetURL() const {
return AddMetadataUrlParams(GURL(kAccountMetadataURL));
}
//============================ DownloadFileOperation ===========================
DownloadFileOperation::DownloadFileOperation(
OperationRegistry* registry,
const DownloadActionCallback& download_action_callback,
const GetContentCallback& get_content_callback,
const GURL& document_url,
const FilePath& virtual_path,
const FilePath& output_file_path)
: UrlFetchOperationBase(registry,
OPERATION_DOWNLOAD,
virtual_path),
download_action_callback_(download_action_callback),
get_content_callback_(get_content_callback),
document_url_(document_url) {
// Make sure we download the content into a temp file.
if (output_file_path.empty())
save_temp_file_ = true;
else
output_file_path_ = output_file_path;
}
DownloadFileOperation::~DownloadFileOperation() {}
// Overridden from UrlFetchOperationBase.
GURL DownloadFileOperation::GetURL() const {
return document_url_;
}
void DownloadFileOperation::OnURLFetchDownloadProgress(const URLFetcher* source,
int64 current,
int64 total) {
NotifyProgress(current, total);
}
bool DownloadFileOperation::ShouldSendDownloadData() {
return !get_content_callback_.is_null();
}
void DownloadFileOperation::OnURLFetchDownloadData(
const URLFetcher* source,
scoped_ptr<std::string> download_data) {
if (!get_content_callback_.is_null())
get_content_callback_.Run(HTTP_SUCCESS, download_data.Pass());
}
void DownloadFileOperation::ProcessURLFetchResults(const URLFetcher* source) {
GDataErrorCode code = GetErrorCode(source);
// Take over the ownership of the the downloaded temp file.
FilePath temp_file;
if (code == HTTP_SUCCESS &&
!source->GetResponseAsFilePath(true, // take_ownership
&temp_file)) {
code = GDATA_FILE_ERROR;
}
if (!download_action_callback_.is_null())
download_action_callback_.Run(code, document_url_, temp_file);
OnProcessURLFetchResultsComplete(code == HTTP_SUCCESS);
}
void DownloadFileOperation::RunCallbackOnPrematureFailure(GDataErrorCode code) {
if (!download_action_callback_.is_null())
download_action_callback_.Run(code, document_url_, FilePath());
}
//=========================== DeleteDocumentOperation ==========================
DeleteDocumentOperation::DeleteDocumentOperation(
OperationRegistry* registry,
const EntryActionCallback& callback,
const GURL& document_url)
: EntryActionOperation(registry, callback, document_url) {
}
DeleteDocumentOperation::~DeleteDocumentOperation() {}
GURL DeleteDocumentOperation::GetURL() const {
return AddStandardUrlParams(document_url());
}
URLFetcher::RequestType DeleteDocumentOperation::GetRequestType() const {
return URLFetcher::DELETE_REQUEST;
}
std::vector<std::string>
DeleteDocumentOperation::GetExtraRequestHeaders() const {
std::vector<std::string> headers;
headers.push_back(kIfMatchAllHeader);
return headers;
}
//========================== CreateDirectoryOperation ==========================
CreateDirectoryOperation::CreateDirectoryOperation(
OperationRegistry* registry,
const GetDataCallback& callback,
const GURL& parent_content_url,
const FilePath::StringType& directory_name)
: GetDataOperation(registry, callback),
parent_content_url_(parent_content_url),
directory_name_(directory_name) {
}
CreateDirectoryOperation::~CreateDirectoryOperation() {}
GURL CreateDirectoryOperation::GetURL() const {
if (!parent_content_url_.is_empty())
return AddStandardUrlParams(parent_content_url_);
return AddStandardUrlParams(GURL(kDocumentListRootURL));
}
URLFetcher::RequestType
CreateDirectoryOperation::GetRequestType() const {
return URLFetcher::POST;
}
bool CreateDirectoryOperation::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", FilePath(directory_name_).AsUTF8Unsafe());
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;
}
//============================ CopyDocumentOperation ===========================
CopyDocumentOperation::CopyDocumentOperation(
OperationRegistry* registry,
const GetDataCallback& callback,
const std::string& resource_id,
const FilePath::StringType& new_name)
: GetDataOperation(registry, callback),
resource_id_(resource_id),
new_name_(new_name) {
}
CopyDocumentOperation::~CopyDocumentOperation() {}
URLFetcher::RequestType CopyDocumentOperation::GetRequestType() const {
return URLFetcher::POST;
}
GURL CopyDocumentOperation::GetURL() const {
return AddStandardUrlParams(GURL(kDocumentListRootURL));
}
bool CopyDocumentOperation::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", FilePath(new_name_).AsUTF8Unsafe());
xml_writer.EndElement(); // Ends "entry" element.
xml_writer.StopWriting();
upload_content->assign(xml_writer.GetWrittenString());
DVLOG(1) << "CopyDocumentOperation data: " << *upload_content_type << ", ["
<< *upload_content << "]";
return true;
}
//=========================== RenameResourceOperation ==========================
RenameResourceOperation::RenameResourceOperation(
OperationRegistry* registry,
const EntryActionCallback& callback,
const GURL& document_url,
const FilePath::StringType& new_name)
: EntryActionOperation(registry, callback, document_url),
new_name_(new_name) {
}
RenameResourceOperation::~RenameResourceOperation() {}
URLFetcher::RequestType RenameResourceOperation::GetRequestType() const {
return URLFetcher::PUT;
}
std::vector<std::string>
RenameResourceOperation::GetExtraRequestHeaders() const {
std::vector<std::string> headers;
headers.push_back(kIfMatchAllHeader);
return headers;
}
GURL RenameResourceOperation::GetURL() const {
return AddStandardUrlParams(document_url());
}
bool RenameResourceOperation::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", FilePath(new_name_).AsUTF8Unsafe());
xml_writer.EndElement(); // Ends "entry" element.
xml_writer.StopWriting();
upload_content->assign(xml_writer.GetWrittenString());
DVLOG(1) << "RenameResourceOperation data: " << *upload_content_type << ", ["
<< *upload_content << "]";
return true;
}
//=========================== AuthorizeAppOperation ==========================
AuthorizeAppsOperation::AuthorizeAppsOperation(
OperationRegistry* registry,
const GetDataCallback& callback,
const GURL& document_url,
const std::string& app_id)
: GetDataOperation(registry, callback),
app_id_(app_id),
document_url_(document_url) {
}
AuthorizeAppsOperation::~AuthorizeAppsOperation() {}
URLFetcher::RequestType AuthorizeAppsOperation::GetRequestType() const {
return URLFetcher::PUT;
}
std::vector<std::string>
AuthorizeAppsOperation::GetExtraRequestHeaders() const {
std::vector<std::string> headers;
headers.push_back(kIfMatchAllHeader);
return headers;
}
void AuthorizeAppsOperation::ProcessURLFetchResults(const URLFetcher* source) {
std::string data;
source->GetResponseAsString(&data);
GetDataOperation::ProcessURLFetchResults(source);
}
bool AuthorizeAppsOperation::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) << "AuthorizeAppOperation data: " << *upload_content_type << ", ["
<< *upload_content << "]";
return true;
}
void AuthorizeAppsOperation::ParseResponse(
GDataErrorCode fetch_error_code,
const std::string& data) {
// Parse entry XML.
XmlReader xml_reader;
scoped_ptr<DocumentEntry> entry;
if (xml_reader.Load(data)) {
while (xml_reader.Read()) {
if (xml_reader.NodeName() == DocumentEntry::GetEntryNodeName()) {
entry.reset(DocumentEntry::CreateFromXml(&xml_reader));
break;
}
}
}
// From the response, we create a list of the links returned, since those
// are the only things we are interested in.
scoped_ptr<base::ListValue> link_list(new ListValue);
const ScopedVector<Link>& feed_links = entry->links();
for (ScopedVector<Link>::const_iterator iter = feed_links.begin();
iter != feed_links.end(); ++iter) {
if ((*iter)->type() == Link::LINK_OPEN_WITH) {
base::DictionaryValue* link = new DictionaryValue;
link->SetString(std::string("href"), (*iter)->href().spec());
link->SetString(std::string("mime_type"), (*iter)->mime_type());
link->SetString(std::string("title"), (*iter)->title());
link->SetString(std::string("app_id"), (*iter)->app_id());
link_list->Append(link);
}
}
RunCallback(fetch_error_code, link_list.PassAs<base::Value>());
const bool success = true;
OnProcessURLFetchResultsComplete(success);
}
GURL AuthorizeAppsOperation::GetURL() const {
return document_url_;
}
//======================= AddResourceToDirectoryOperation ======================
AddResourceToDirectoryOperation::AddResourceToDirectoryOperation(
OperationRegistry* registry,
const EntryActionCallback& callback,
const GURL& parent_content_url,
const GURL& document_url)
: EntryActionOperation(registry, callback, document_url),
parent_content_url_(parent_content_url) {
}
AddResourceToDirectoryOperation::~AddResourceToDirectoryOperation() {}
GURL AddResourceToDirectoryOperation::GetURL() const {
if (!parent_content_url_.is_empty())
return AddStandardUrlParams(parent_content_url_);
return AddStandardUrlParams(GURL(kDocumentListRootURL));
}
URLFetcher::RequestType
AddResourceToDirectoryOperation::GetRequestType() const {
return URLFetcher::POST;
}
bool AddResourceToDirectoryOperation::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", document_url().spec());
xml_writer.EndElement(); // Ends "entry" element.
xml_writer.StopWriting();
upload_content->assign(xml_writer.GetWrittenString());
DVLOG(1) << "AddResourceToDirectoryOperation data: " << *upload_content_type
<< ", [" << *upload_content << "]";
return true;
}
//==================== RemoveResourceFromDirectoryOperation ====================
RemoveResourceFromDirectoryOperation::RemoveResourceFromDirectoryOperation(
OperationRegistry* registry,
const EntryActionCallback& callback,
const GURL& parent_content_url,
const GURL& document_url,
const std::string& document_resource_id)
: EntryActionOperation(registry, callback, document_url),
resource_id_(document_resource_id),
parent_content_url_(parent_content_url) {
}
RemoveResourceFromDirectoryOperation::~RemoveResourceFromDirectoryOperation() {
}
GURL RemoveResourceFromDirectoryOperation::GetURL() const {
std::string escaped_resource_id = net::EscapePath(resource_id_);
GURL edit_url(base::StringPrintf("%s/%s",
parent_content_url_.spec().c_str(),
escaped_resource_id.c_str()));
return AddStandardUrlParams(edit_url);
}
URLFetcher::RequestType
RemoveResourceFromDirectoryOperation::GetRequestType() const {
return URLFetcher::DELETE_REQUEST;
}
std::vector<std::string>
RemoveResourceFromDirectoryOperation::GetExtraRequestHeaders() const {
std::vector<std::string> headers;
headers.push_back(kIfMatchAllHeader);
return headers;
}
//=========================== InitiateUploadOperation ==========================
InitiateUploadOperation::InitiateUploadOperation(
OperationRegistry* registry,
const InitiateUploadCallback& callback,
const InitiateUploadParams& params)
: UrlFetchOperationBase(registry,
OPERATION_UPLOAD,
params.virtual_path),
callback_(callback),
params_(params),
initiate_upload_url_(chrome_common_net::AppendOrReplaceQueryParameter(
params.upload_location,
kUploadParamConvertKey,
kUploadParamConvertValue)) {
}
InitiateUploadOperation::~InitiateUploadOperation() {}
GURL InitiateUploadOperation::GetURL() const {
return initiate_upload_url_;
}
void InitiateUploadOperation::ProcessURLFetchResults(
const URLFetcher* source) {
GDataErrorCode code = GetErrorCode(source);
std::string upload_location;
if (code == HTTP_SUCCESS) {
// Retrieve value of the first "Location" header.
source->GetResponseHeaders()->EnumerateHeader(NULL,
kUploadResponseLocation,
&upload_location);
}
VLOG(1) << "Got response for [" << params_.title
<< "]: code=" << code
<< ", location=[" << upload_location << "]";
if (!callback_.is_null())
callback_.Run(code, GURL(upload_location));
OnProcessURLFetchResultsComplete(code == HTTP_SUCCESS);
}
void InitiateUploadOperation::NotifySuccessToOperationRegistry() {
NotifySuspend();
}
void InitiateUploadOperation::RunCallbackOnPrematureFailure(
GDataErrorCode code) {
if (!callback_.is_null())
callback_.Run(code, GURL());
}
URLFetcher::RequestType InitiateUploadOperation::GetRequestType() const {
if (params_.upload_mode == UPLOAD_NEW_FILE)
return URLFetcher::POST;
DCHECK_EQ(UPLOAD_EXISTING_FILE, params_.upload_mode);
return URLFetcher::PUT;
}
std::vector<std::string>
InitiateUploadOperation::GetExtraRequestHeaders() const {
std::vector<std::string> headers;
if (!params_.content_type.empty())
headers.push_back(kUploadContentType + params_.content_type);
headers.push_back(
kUploadContentLength + base::Int64ToString(params_.content_length));
if (params_.upload_mode == UPLOAD_EXISTING_FILE)
headers.push_back("If-Match: *");
return headers;
}
bool InitiateUploadOperation::GetContentData(std::string* upload_content_type,
std::string* upload_content) {
if (params_.upload_mode == UPLOAD_EXISTING_FILE) {
// When uploading an existing file, the body is empty as we don't modify
// the metadata.
*upload_content = "";
// Even though the body is empty, Content-Type should be set to
// "text/plain". Otherwise, the server won't accept.
*upload_content_type = "text/plain";
return true;
}
DCHECK_EQ(UPLOAD_NEW_FILE, params_.upload_mode);
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", params_.title);
xml_writer.EndElement(); // Ends "entry" element.
xml_writer.StopWriting();
upload_content->assign(xml_writer.GetWrittenString());
DVLOG(1) << "Upload data: " << *upload_content_type << ", ["
<< *upload_content << "]";
return true;
}
//============================ ResumeUploadOperation ===========================
ResumeUploadOperation::ResumeUploadOperation(
OperationRegistry* registry,
const ResumeUploadCallback& callback,
const ResumeUploadParams& params)
: UrlFetchOperationBase(registry,
OPERATION_UPLOAD,
params.virtual_path),
callback_(callback),
params_(params),
last_chunk_completed_(false) {
}
ResumeUploadOperation::~ResumeUploadOperation() {}
GURL ResumeUploadOperation::GetURL() const {
return params_.upload_location;
}
void ResumeUploadOperation::ProcessURLFetchResults(const URLFetcher* source) {
GDataErrorCode code = GetErrorCode(source);
net::HttpResponseHeaders* hdrs = source->GetResponseHeaders();
int64 start_range_received = -1;
int64 end_range_received = -1;
scoped_ptr<DocumentEntry> entry;
if (code == HTTP_RESUME_INCOMPLETE) {
// Retrieve value of the first "Range" header.
std::string range_received;
hdrs->EnumerateHeader(NULL, kUploadResponseRange, &range_received);
if (!range_received.empty()) { // Parse the range header.
std::vector<net::HttpByteRange> ranges;
if (net::HttpUtil::ParseRangeHeader(range_received, &ranges) &&
!ranges.empty() ) {
// We only care about the first start-end pair in the range.
start_range_received = ranges[0].first_byte_position();
end_range_received = ranges[0].last_byte_position();
}
}
DVLOG(1) << "Got response for [" << params_.virtual_path.value()
<< "]: code=" << code
<< ", range_hdr=[" << range_received
<< "], range_parsed=" << start_range_received
<< "," << end_range_received;
} else {
// There might be explanation of unexpected error code in response.
std::string response_content;
source->GetResponseAsString(&response_content);
DVLOG(1) << "Got response for [" << params_.virtual_path.value()
<< "]: code=" << code
<< ", content=[\n" << response_content << "\n]";
// Parse entry XML.
XmlReader xml_reader;
if (xml_reader.Load(response_content)) {
while (xml_reader.Read()) {
if (xml_reader.NodeName() == DocumentEntry::GetEntryNodeName()) {
entry.reset(DocumentEntry::CreateFromXml(&xml_reader));
break;
}
}
}
if (!entry.get())
LOG(WARNING) << "Invalid entry received on upload:\n" << response_content;
}
if (!callback_.is_null()) {
callback_.Run(ResumeUploadResponse(code,
start_range_received,
end_range_received),
entry.Pass());
}
// For a new file, HTTP_CREATED is returned.
// For an existing file, HTTP_SUCCESS is returned.
if ((params_.upload_mode == UPLOAD_NEW_FILE && code == HTTP_CREATED) ||
(params_.upload_mode == UPLOAD_EXISTING_FILE && code == HTTP_SUCCESS)) {
last_chunk_completed_ = true;
}
OnProcessURLFetchResultsComplete(
last_chunk_completed_ || code == HTTP_RESUME_INCOMPLETE);
}
void ResumeUploadOperation::NotifyStartToOperationRegistry() {
NotifyResume();
}
void ResumeUploadOperation::NotifySuccessToOperationRegistry() {
if (last_chunk_completed_)
NotifyFinish(OPERATION_COMPLETED);
else
NotifySuspend();
}
void ResumeUploadOperation::RunCallbackOnPrematureFailure(GDataErrorCode code) {
scoped_ptr<DocumentEntry> entry;
if (!callback_.is_null())
callback_.Run(ResumeUploadResponse(code, 0, 0), entry.Pass());
}
URLFetcher::RequestType ResumeUploadOperation::GetRequestType() const {
return URLFetcher::PUT;
}
std::vector<std::string> ResumeUploadOperation::GetExtraRequestHeaders() const {
if (params_.content_length == 0) {
// For uploading an empty document, just PUT an empty content.
DCHECK_EQ(params_.start_range, 0);
DCHECK_EQ(params_.end_range, -1);
return std::vector<std::string>();
}
// The header looks like
// Content-Range: bytes <start_range>-<end_range>/<content_length>
// for example:
// Content-Range: bytes 7864320-8388607/13851821
// Use * for unknown/streaming content length.
DCHECK_GE(params_.start_range, 0);
DCHECK_GE(params_.end_range, 0);
DCHECK_GE(params_.content_length, -1);
std::vector<std::string> headers;
headers.push_back(
std::string(kUploadContentRange) +
base::Int64ToString(params_.start_range) + "-" +
base::Int64ToString(params_.end_range) + "/" +
(params_.content_length == -1 ? "*" :
base::Int64ToString(params_.content_length)));
return headers;
}
bool ResumeUploadOperation::GetContentData(std::string* upload_content_type,
std::string* upload_content) {
*upload_content_type = params_.content_type;
*upload_content = std::string(params_.buf->data(),
params_.end_range - params_.start_range + 1);
return true;
}
void ResumeUploadOperation::OnURLFetchUploadProgress(
const URLFetcher* source, int64 current, int64 total) {
// Adjust the progress values according to the range currently uploaded.
NotifyProgress(params_.start_range + current, params_.content_length);
}
//========================== GetContactGroupsOperation =========================
GetContactGroupsOperation::GetContactGroupsOperation(
OperationRegistry* registry,
const GetDataCallback& callback)
: GetDataOperation(registry, callback) {
}
GetContactGroupsOperation::~GetContactGroupsOperation() {}
GURL GetContactGroupsOperation::GetURL() const {
return !feed_url_for_testing_.is_empty() ?
feed_url_for_testing_ :
GURL(kGetContactGroupsURL);
}
//============================ GetContactsOperation ============================
GetContactsOperation::GetContactsOperation(OperationRegistry* registry,
const std::string& group_id,
const base::Time& min_update_time,
const GetDataCallback& callback)
: GetDataOperation(registry, callback),
group_id_(group_id),
min_update_time_(min_update_time) {
}
GetContactsOperation::~GetContactsOperation() {}
GURL GetContactsOperation::GetURL() const {
if (!feed_url_for_testing_.is_empty())
return GURL(feed_url_for_testing_);
GURL url(kGetContactsURL);
if (!group_id_.empty()) {
url = chrome_common_net::AppendQueryParameter(
url, kGetContactsGroupParam, group_id_);
}
if (!min_update_time_.is_null()) {
std::string time_rfc3339 = util::FormatTimeAsString(min_update_time_);
url = chrome_common_net::AppendQueryParameter(
url, kGetContactsUpdatedMinParam, time_rfc3339);
}
return url;
}
//========================== GetContactPhotoOperation ==========================
GetContactPhotoOperation::GetContactPhotoOperation(
OperationRegistry* registry,
const GURL& photo_url,
const GetContentCallback& callback)
: UrlFetchOperationBase(registry),
photo_url_(photo_url),
callback_(callback) {
}
GetContactPhotoOperation::~GetContactPhotoOperation() {}
GURL GetContactPhotoOperation::GetURL() const {
return photo_url_;
}
void GetContactPhotoOperation::ProcessURLFetchResults(
const net::URLFetcher* source) {
GDataErrorCode code = static_cast<GDataErrorCode>(source->GetResponseCode());
scoped_ptr<std::string> data(new std::string);
source->GetResponseAsString(data.get());
callback_.Run(code, data.Pass());
OnProcessURLFetchResultsComplete(code == HTTP_SUCCESS);
}
void GetContactPhotoOperation::RunCallbackOnPrematureFailure(
GDataErrorCode code) {
scoped_ptr<std::string> data(new std::string);
callback_.Run(code, data.Pass());
}
} // namespace gdata