summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/google_apis/drive_api_operations.cc64
-rw-r--r--chrome/browser/google_apis/drive_api_operations.h38
-rw-r--r--chrome/browser/google_apis/drive_api_operations_unittest.cc582
-rw-r--r--chrome/browser/google_apis/drive_api_service.cc43
-rw-r--r--chrome/browser/google_apis/gdata_wapi_operations_unittest.cc42
-rw-r--r--chrome/browser/google_apis/test_util.cc32
-rw-r--r--chrome/browser/google_apis/test_util.h8
-rw-r--r--chrome/test/data/chromeos/drive/file_entry.json49
8 files changed, 785 insertions, 73 deletions
diff --git a/chrome/browser/google_apis/drive_api_operations.cc b/chrome/browser/google_apis/drive_api_operations.cc
index 56044c1..492c3ec 100644
--- a/chrome/browser/google_apis/drive_api_operations.cc
+++ b/chrome/browser/google_apis/drive_api_operations.cc
@@ -32,7 +32,7 @@ void ParseJsonAndRun(
DCHECK(!callback.is_null());
scoped_ptr<T> resource;
- if (value.get()) {
+ if (value) {
resource = T::CreateFrom(*value);
if (!resource) {
// Failed to parse the JSON value, although the JSON value is available,
@@ -44,6 +44,33 @@ void ParseJsonAndRun(
callback.Run(error, resource.Pass());
}
+// Parses the JSON value to FileResource instance and runs |callback| on the
+// UI thread once parsing is done.
+// This is customized version of ParseJsonAndRun defined above to adapt the
+// remaining response type.
+void ParseFileResourceWithUploadRangeAndRun(
+ const drive::UploadRangeCallback& callback,
+ const UploadRangeResponse& response,
+ scoped_ptr<base::Value> value) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(!callback.is_null());
+
+ scoped_ptr<FileResource> file_resource;
+ if (value) {
+ file_resource = FileResource::CreateFrom(*value);
+ if (!file_resource) {
+ callback.Run(
+ UploadRangeResponse(GDATA_PARSE_ERROR,
+ response.start_position_received,
+ response.end_position_received),
+ scoped_ptr<FileResource>());
+ return;
+ }
+ }
+
+ callback.Run(response, file_resource.Pass());
+}
+
} // namespace
//============================== GetAboutOperation =============================
@@ -441,5 +468,40 @@ InitiateUploadExistingFileOperation::GetExtraRequestHeaders() const {
return headers;
}
+//============================ ResumeUploadOperation ===========================
+
+ResumeUploadOperation::ResumeUploadOperation(
+ OperationRegistry* registry,
+ net::URLRequestContextGetter* url_request_context_getter,
+ UploadMode upload_mode,
+ 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 scoped_refptr<net::IOBuffer>& buf,
+ const UploadRangeCallback& callback)
+ : ResumeUploadOperationBase(registry,
+ url_request_context_getter,
+ upload_mode,
+ drive_file_path,
+ upload_location,
+ start_position,
+ end_position,
+ content_length,
+ content_type,
+ buf),
+ callback_(callback) {
+ DCHECK(!callback_.is_null());
+}
+
+ResumeUploadOperation::~ResumeUploadOperation() {}
+
+void ResumeUploadOperation::OnRangeOperationComplete(
+ const UploadRangeResponse& response, scoped_ptr<base::Value> value) {
+ ParseFileResourceWithUploadRangeAndRun(callback_, response, value.Pass());
+}
+
} // namespace drive
} // namespace google_apis
diff --git a/chrome/browser/google_apis/drive_api_operations.h b/chrome/browser/google_apis/drive_api_operations.h
index 504371c..2b7abb1 100644
--- a/chrome/browser/google_apis/drive_api_operations.h
+++ b/chrome/browser/google_apis/drive_api_operations.h
@@ -392,6 +392,44 @@ class InitiateUploadExistingFileOperation
DISALLOW_COPY_AND_ASSIGN(InitiateUploadExistingFileOperation);
};
+// Callback used for ResumeUpload() (and will be used for GetUploadStatus()).
+typedef base::Callback<void(
+ const UploadRangeResponse& response,
+ scoped_ptr<FileResource> new_resource)> UploadRangeCallback;
+
+//============================ ResumeUploadOperation ===========================
+
+// Performs the operation for resuming the upload of a file.
+class ResumeUploadOperation : public ResumeUploadOperationBase {
+ public:
+ // See also ResumeUploadOperationBase's comment for parameters meaining.
+ // |callback| must not be null.
+ ResumeUploadOperation(
+ OperationRegistry* registry,
+ net::URLRequestContextGetter* url_request_context_getter,
+ UploadMode upload_mode,
+ 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 scoped_refptr<net::IOBuffer>& buf,
+ const UploadRangeCallback& callback);
+ virtual ~ResumeUploadOperation();
+
+ protected:
+ // UploadRangeOperationBase overrides.
+ virtual void OnRangeOperationComplete(
+ const UploadRangeResponse& response,
+ scoped_ptr<base::Value> value) OVERRIDE;
+
+ private:
+ const UploadRangeCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResumeUploadOperation);
+};
+
} // namespace drive
} // namespace google_apis
diff --git a/chrome/browser/google_apis/drive_api_operations_unittest.cc b/chrome/browser/google_apis/drive_api_operations_unittest.cc
index 4f0ab52..b6b95c6 100644
--- a/chrome/browser/google_apis/drive_api_operations_unittest.cc
+++ b/chrome/browser/google_apis/drive_api_operations_unittest.cc
@@ -24,6 +24,7 @@ namespace google_apis {
namespace {
const char kTestDriveApiAuthToken[] = "testtoken";
+const char kTestETag[] = "test_etag";
const char kTestUserAgent[] = "test-user-agent";
const char kTestChildrenResponse[] =
@@ -34,7 +35,8 @@ const char kTestChildrenResponse[] =
"\"childLink\": \"child_link\",\n"
"}\n";
-const char kTestUploadUrl[] = "https://server/upload/path";
+const char kTestUploadExistingFilePath[] = "/upload/existingfile/path";
+const char kTestUploadNewFilePath[] = "/upload/newfile/path";
void CopyResultsFromGetAboutResourceCallbackAndQuit(
GDataErrorCode* error_out,
@@ -56,6 +58,16 @@ void CopyResultsFromFileResourceCallbackAndQuit(
MessageLoop::current()->Quit();
}
+void CopyResultFromUploadRangeCallbackAndQuit(
+ UploadRangeResponse* response_out,
+ scoped_ptr<FileResource>* file_resource_out,
+ const UploadRangeResponse& response_in,
+ scoped_ptr<FileResource> file_resource_in) {
+ *response_out = response_in;
+ *file_resource_out = file_resource_in.Pass();
+ MessageLoop::current()->Quit();
+}
+
} // namespace
class DriveApiOperationsTest : public testing::Test {
@@ -82,6 +94,9 @@ class DriveApiOperationsTest : public testing::Test {
base::Bind(&DriveApiOperationsTest::HandleDataFileRequest,
base::Unretained(this)));
test_server_.RegisterRequestHandler(
+ base::Bind(&DriveApiOperationsTest::HandleResumeUploadRequest,
+ base::Unretained(this)));
+ test_server_.RegisterRequestHandler(
base::Bind(&DriveApiOperationsTest::HandleInitiateUploadRequest,
base::Unretained(this)));
test_server_.RegisterRequestHandler(
@@ -93,6 +108,8 @@ class DriveApiOperationsTest : public testing::Test {
// Reset the server's expected behavior just in case.
ResetExpectedResponse();
+ received_bytes_ = 0;
+ content_length_ = 0;
}
virtual void TearDown() OVERRIDE {
@@ -114,9 +131,9 @@ class DriveApiOperationsTest : public testing::Test {
// the server. See also HandleDataFileRequest below.
base::FilePath expected_data_file_path_;
- // This is a url string in the expected response header from the server
+ // This is a path string in the expected response header from the server
// for initiating file uploading.
- std::string expected_upload_url_;
+ std::string expected_upload_path_;
// These are content and its type in the expected response from the server.
// See also HandleContentResponse below.
@@ -131,7 +148,7 @@ class DriveApiOperationsTest : public testing::Test {
private:
void ResetExpectedResponse() {
expected_data_file_path_.clear();
- expected_upload_url_.clear();
+ expected_upload_path_.clear();
expected_content_type_.clear();
expected_content_.clear();
}
@@ -177,13 +194,14 @@ class DriveApiOperationsTest : public testing::Test {
// Returns the response based on set expected upload url.
// The response contains the url in its "Location: " header. Also, it doesn't
// have any content.
- // To use this method, it is necessary to set |expected_upload_url_|
+ // To use this method, it is necessary to set |expected_upload_path_|
// to the string representation of the url to be returned.
scoped_ptr<test_server::HttpResponse> HandleInitiateUploadRequest(
const test_server::HttpRequest& request) {
- if (expected_upload_url_.empty()) {
- // Expected upload url is not set. Delegate the processing to the next
- // handler.
+ if (request.relative_url == expected_upload_path_ ||
+ expected_upload_path_.empty()) {
+ // The request is for resume uploading or the expected upload url is not
+ // set. Delegate the processing to the next handler.
return scoped_ptr<test_server::HttpResponse>();
}
@@ -191,8 +209,95 @@ class DriveApiOperationsTest : public testing::Test {
scoped_ptr<test_server::HttpResponse> response(
new test_server::HttpResponse);
+
+ // Check an ETag.
+ std::map<std::string, std::string>::const_iterator found =
+ request.headers.find("If-Match");
+ if (found != request.headers.end() &&
+ found->second != "*" &&
+ found->second != kTestETag) {
+ response->set_code(test_server::PRECONDITION);
+ return response.Pass();
+ }
+
+ // Check if the X-Upload-Content-Length is present. If yes, store the
+ // length of the file.
+ found = request.headers.find("X-Upload-Content-Length");
+ if (found == request.headers.end() ||
+ !base::StringToInt64(found->second, &content_length_)) {
+ return scoped_ptr<test_server::HttpResponse>();
+ }
+ received_bytes_ = 0;
+
response->set_code(test_server::SUCCESS);
- response->AddCustomHeader("Location", expected_upload_url_);
+ response->AddCustomHeader(
+ "Location",
+ test_server_.base_url().Resolve(expected_upload_path_).spec());
+ return response.Pass();
+ }
+
+ scoped_ptr<test_server::HttpResponse> HandleResumeUploadRequest(
+ const test_server::HttpRequest& request) {
+ if (request.relative_url != expected_upload_path_) {
+ // The request path is different from the expected path for uploading.
+ // Delegate the processing to the next handler.
+ return scoped_ptr<test_server::HttpResponse>();
+ }
+
+ http_request_ = request;
+
+ if (!request.content.empty()) {
+ std::map<std::string, std::string>::const_iterator iter =
+ request.headers.find("Content-Range");
+ if (iter == request.headers.end()) {
+ // The range must be set.
+ return scoped_ptr<test_server::HttpResponse>();
+ }
+
+ int64 length = 0;
+ int64 start_position = 0;
+ int64 end_position = 0;
+ if (!test_util::ParseContentRangeHeader(
+ iter->second, &start_position, &end_position, &length)) {
+ // Invalid "Content-Range" value.
+ return scoped_ptr<test_server::HttpResponse>();
+ }
+
+ EXPECT_EQ(start_position, received_bytes_);
+ EXPECT_EQ(length, content_length_);
+
+ // end_position is inclusive, but so +1 to change the range to byte size.
+ received_bytes_ = end_position + 1;
+ }
+
+ if (received_bytes_ < content_length_) {
+ scoped_ptr<test_server::HttpResponse> response(
+ new test_server::HttpResponse);
+ // Set RESUME INCOMPLETE (308) status code.
+ response->set_code(test_server::RESUME_INCOMPLETE);
+
+ // Add Range header to the response, based on the values of
+ // Content-Range header in the request.
+ // The header is annotated only when at least one byte is received.
+ if (received_bytes_ > 0) {
+ response->AddCustomHeader(
+ "Range", "bytes=0-" + base::Int64ToString(received_bytes_ - 1));
+ }
+
+ return response.Pass();
+ }
+
+ // All bytes are received. Return the "success" response with the file's
+ // (dummy) metadata.
+ scoped_ptr<test_server::HttpResponse> response =
+ test_util::CreateHttpResponseFromFile(
+ test_util::GetTestFilePath("drive/file_entry.json"));
+
+ // The response code is CREATED if it is new file uploading.
+ if (http_request_.relative_url == kTestUploadNewFilePath) {
+ response->set_code(test_server::CREATED);
+ }
+
return response.Pass();
}
@@ -216,6 +321,10 @@ class DriveApiOperationsTest : public testing::Test {
response->set_content(expected_content_);
return response.Pass();
}
+
+ // These are for the current upload file status.
+ int64 received_bytes_;
+ int64 content_length_;
};
TEST_F(DriveApiOperationsTest, GetAboutOperation_ValidFeed) {
@@ -430,17 +539,18 @@ TEST_F(DriveApiOperationsTest, DeleteResourceOperation) {
EXPECT_FALSE(http_request_.has_content);
}
-TEST_F(DriveApiOperationsTest, InitiateUploadNewFileOperation) {
+TEST_F(DriveApiOperationsTest, UploadNewFileOperation) {
// Set an expected url for uploading.
- expected_upload_url_ = kTestUploadUrl;
+ expected_upload_path_ = kTestUploadNewFilePath;
const char kTestContentType[] = "text/plain";
- const int64 kTestContentLength = 100;
+ const std::string kTestContent(100, 'a');
GDataErrorCode error = GDATA_OTHER_ERROR;
- GURL url;
+ GURL upload_url;
- // Initiate uploading a new file to the directory with "parent_resource_id".
+ // 1) Initiate uploading a new file to the directory with
+ // "parent_resource_id".
drive::InitiateUploadNewFileOperation* operation =
new drive::InitiateUploadNewFileOperation(
&operation_registry_,
@@ -448,19 +558,19 @@ TEST_F(DriveApiOperationsTest, InitiateUploadNewFileOperation) {
*url_generator_,
base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
kTestContentType,
- kTestContentLength,
+ kTestContent.size(),
"parent_resource_id", // The resource id of the parent directory.
"new file title", // The title of the file being uploaded.
base::Bind(&test_util::CopyResultsFromInitiateUploadCallbackAndQuit,
- &error, &url));
+ &error, &upload_url));
operation->Start(kTestDriveApiAuthToken, kTestUserAgent,
base::Bind(&test_util::DoNothingForReAuthenticateCallback));
MessageLoop::current()->Run();
EXPECT_EQ(HTTP_SUCCESS, error);
- EXPECT_EQ(kTestUploadUrl, url.spec());
+ EXPECT_EQ(kTestUploadNewFilePath, upload_url.path());
EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kTestContentLength),
+ EXPECT_EQ(base::Int64ToString(kTestContent.size()),
http_request_.headers["X-Upload-Content-Length"]);
EXPECT_EQ(test_server::METHOD_POST, http_request_.method);
@@ -475,19 +585,259 @@ TEST_F(DriveApiOperationsTest, InitiateUploadNewFileOperation) {
"\"title\":\"new file title\"}",
http_request_.content);
- // Clean the operation remaining in |operation_registry_|.
- operation_registry_.CancelAll();
+ // 2) Upload the content to the upload URL.
+ scoped_refptr<net::IOBuffer> buffer = new net::StringIOBuffer(kTestContent);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> new_entry;
+
+ drive::ResumeUploadOperation* resume_operation =
+ new drive::ResumeUploadOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ UPLOAD_NEW_FILE,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ upload_url,
+ 0, // start_position
+ kTestContent.size(), // end_position (exclusive)
+ kTestContent.size(), // content_length,
+ kTestContentType,
+ buffer,
+ base::Bind(&CopyResultFromUploadRangeCallbackAndQuit,
+ &response, &new_entry));
+
+ resume_operation->Start(
+ kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ // METHOD_PUT should be used to upload data.
+ EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
+ // Request should go to the upload URL.
+ EXPECT_EQ(upload_url.path(), http_request_.relative_url);
+ // Content-Range header should be added.
+ EXPECT_EQ("bytes 0-" +
+ base::Int64ToString(kTestContent.size() - 1) + "/" +
+ base::Int64ToString(kTestContent.size()),
+ http_request_.headers["Content-Range"]);
+ // The upload content should be set in the HTTP request.
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ(kTestContent, http_request_.content);
+
+ // Check the response.
+ EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file
+ // The start and end positions should be set to -1, if an upload is complete.
+ EXPECT_EQ(-1, response.start_position_received);
+ EXPECT_EQ(-1, response.end_position_received);
+}
+
+TEST_F(DriveApiOperationsTest, UploadNewEmptyFileOperation) {
+ // Set an expected url for uploading.
+ expected_upload_path_ = kTestUploadNewFilePath;
+
+ const char kTestContentType[] = "text/plain";
+ const char kTestContent[] = "";
+
+ GDataErrorCode error = GDATA_OTHER_ERROR;
+ GURL upload_url;
+
+ // 1) Initiate uploading a new file to the directory with
+ // "parent_resource_id".
+ drive::InitiateUploadNewFileOperation* operation =
+ new drive::InitiateUploadNewFileOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ *url_generator_,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ kTestContentType,
+ 0,
+ "parent_resource_id", // The resource id of the parent directory.
+ "new file title", // The title of the file being uploaded.
+ base::Bind(&test_util::CopyResultsFromInitiateUploadCallbackAndQuit,
+ &error, &upload_url));
+ operation->Start(kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(kTestUploadNewFilePath, upload_url.path());
+ EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
+ EXPECT_EQ("0", http_request_.headers["X-Upload-Content-Length"]);
+
+ EXPECT_EQ(test_server::METHOD_POST, http_request_.method);
+ EXPECT_EQ("/upload/drive/v2/files?uploadType=resumable",
+ http_request_.relative_url);
+ EXPECT_EQ("application/json", http_request_.headers["Content-Type"]);
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ("{\"parents\":[{"
+ "\"id\":\"parent_resource_id\","
+ "\"kind\":\"drive#fileLink\""
+ "}],"
+ "\"title\":\"new file title\"}",
+ http_request_.content);
+
+ // 2) Upload the content to the upload URL.
+ scoped_refptr<net::IOBuffer> buffer = new net::StringIOBuffer(kTestContent);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> new_entry;
+
+ drive::ResumeUploadOperation* resume_operation =
+ new drive::ResumeUploadOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ UPLOAD_NEW_FILE,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ upload_url,
+ 0, // start_position
+ 0, // end_position (exclusive)
+ 0, // content_length,
+ kTestContentType,
+ buffer,
+ base::Bind(&CopyResultFromUploadRangeCallbackAndQuit,
+ &response, &new_entry));
+
+ resume_operation->Start(
+ kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ // METHOD_PUT should be used to upload data.
+ EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
+ // Request should go to the upload URL.
+ EXPECT_EQ(upload_url.path(), http_request_.relative_url);
+ // Content-Range header should NOT be added.
+ EXPECT_EQ(0U, http_request_.headers.count("Content-Range"));
+ // The upload content should be set in the HTTP request.
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ(kTestContent, http_request_.content);
+
+ // Check the response.
+ EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file
+ // The start and end positions should be set to -1, if an upload is complete.
+ EXPECT_EQ(-1, response.start_position_received);
+ EXPECT_EQ(-1, response.end_position_received);
}
-TEST_F(DriveApiOperationsTest, InitiateUploadExistingFileOperation) {
+TEST_F(DriveApiOperationsTest, UploadNewLargeFileOperation) {
// Set an expected url for uploading.
- expected_upload_url_ = kTestUploadUrl;
+ expected_upload_path_ = kTestUploadNewFilePath;
const char kTestContentType[] = "text/plain";
- const int64 kTestContentLength = 100;
+ const size_t kNumChunkBytes = 10; // Num bytes in a chunk.
+ const std::string kTestContent(100, 'a');
GDataErrorCode error = GDATA_OTHER_ERROR;
- GURL url;
+ GURL upload_url;
+
+ // 1) Initiate uploading a new file to the directory with
+ // "parent_resource_id".
+ drive::InitiateUploadNewFileOperation* operation =
+ new drive::InitiateUploadNewFileOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ *url_generator_,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ kTestContentType,
+ kTestContent.size(),
+ "parent_resource_id", // The resource id of the parent directory.
+ "new file title", // The title of the file being uploaded.
+ base::Bind(&test_util::CopyResultsFromInitiateUploadCallbackAndQuit,
+ &error, &upload_url));
+ operation->Start(kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(kTestUploadNewFilePath, upload_url.path());
+ EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
+ EXPECT_EQ(base::Int64ToString(kTestContent.size()),
+ http_request_.headers["X-Upload-Content-Length"]);
+
+ EXPECT_EQ(test_server::METHOD_POST, http_request_.method);
+ EXPECT_EQ("/upload/drive/v2/files?uploadType=resumable",
+ http_request_.relative_url);
+ EXPECT_EQ("application/json", http_request_.headers["Content-Type"]);
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ("{\"parents\":[{"
+ "\"id\":\"parent_resource_id\","
+ "\"kind\":\"drive#fileLink\""
+ "}],"
+ "\"title\":\"new file title\"}",
+ http_request_.content);
+
+ // 2) Upload the content to the upload URL.
+ for (size_t start_position = 0; start_position < kTestContent.size();
+ start_position += kNumChunkBytes) {
+ const std::string payload = kTestContent.substr(
+ start_position,
+ std::min(kNumChunkBytes, kTestContent.size() - start_position));
+ const size_t end_position = start_position + payload.size();
+ scoped_refptr<net::IOBuffer> buffer = new net::StringIOBuffer(payload);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> new_entry;
+
+ drive::ResumeUploadOperation* resume_operation =
+ new drive::ResumeUploadOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ UPLOAD_NEW_FILE,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ upload_url,
+ start_position,
+ end_position,
+ kTestContent.size(), // content_length,
+ kTestContentType,
+ buffer,
+ base::Bind(&CopyResultFromUploadRangeCallbackAndQuit,
+ &response, &new_entry));
+
+ resume_operation->Start(
+ kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ // METHOD_PUT should be used to upload data.
+ EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
+ // Request should go to the upload URL.
+ EXPECT_EQ(upload_url.path(), http_request_.relative_url);
+ // Content-Range header should be added.
+ EXPECT_EQ("bytes " +
+ base::Int64ToString(start_position) + "-" +
+ base::Int64ToString(end_position - 1) + "/" +
+ base::Int64ToString(kTestContent.size()),
+ http_request_.headers["Content-Range"]);
+ // The upload content should be set in the HTTP request.
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ(payload, http_request_.content);
+
+ if (end_position == kTestContent.size()) {
+ // Check the response.
+ EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file
+ // The start and end positions should be set to -1, if an upload is
+ // complete.
+ EXPECT_EQ(-1, response.start_position_received);
+ EXPECT_EQ(-1, response.end_position_received);
+ } else {
+ // Check the response.
+ EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code);
+ EXPECT_EQ(0, response.start_position_received);
+ EXPECT_EQ(static_cast<int64>(end_position),
+ response.end_position_received);
+ }
+ }
+}
+
+TEST_F(DriveApiOperationsTest, UploadExistingFileOperation) {
+ // Set an expected url for uploading.
+ expected_upload_path_ = kTestUploadExistingFilePath;
+
+ const char kTestContentType[] = "text/plain";
+ const std::string kTestContent(100, 'a');
+
+ GDataErrorCode error = GDATA_OTHER_ERROR;
+ GURL upload_url;
// Initiate uploading a new file to the directory with "parent_resource_id".
drive::InitiateUploadExistingFileOperation* operation =
@@ -497,21 +847,21 @@ TEST_F(DriveApiOperationsTest, InitiateUploadExistingFileOperation) {
*url_generator_,
base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
kTestContentType,
- kTestContentLength,
+ kTestContent.size(),
"resource_id", // The resource id of the file to be overwritten.
- "dummy-etag",
+ "", // No etag.
base::Bind(&test_util::CopyResultsFromInitiateUploadCallbackAndQuit,
- &error, &url));
+ &error, &upload_url));
operation->Start(kTestDriveApiAuthToken, kTestUserAgent,
base::Bind(&test_util::DoNothingForReAuthenticateCallback));
MessageLoop::current()->Run();
EXPECT_EQ(HTTP_SUCCESS, error);
- EXPECT_EQ(kTestUploadUrl, url.spec());
+ EXPECT_EQ(kTestUploadExistingFilePath, upload_url.path());
EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
- EXPECT_EQ(base::Int64ToString(kTestContentLength),
+ EXPECT_EQ(base::Int64ToString(kTestContent.size()),
http_request_.headers["X-Upload-Content-Length"]);
- EXPECT_EQ("dummy-etag", http_request_.headers["If-Match"]);
+ EXPECT_EQ("*", http_request_.headers["If-Match"]);
EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
EXPECT_EQ("/upload/drive/v2/files/resource_id?uploadType=resumable",
@@ -519,8 +869,176 @@ TEST_F(DriveApiOperationsTest, InitiateUploadExistingFileOperation) {
EXPECT_TRUE(http_request_.has_content);
EXPECT_TRUE(http_request_.content.empty());
- // Clean the operation remaining in |operation_registry_|.
- operation_registry_.CancelAll();
+ // 2) Upload the content to the upload URL.
+ scoped_refptr<net::IOBuffer> buffer = new net::StringIOBuffer(kTestContent);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> new_entry;
+
+ drive::ResumeUploadOperation* resume_operation =
+ new drive::ResumeUploadOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ UPLOAD_EXISTING_FILE,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ upload_url,
+ 0, // start_position
+ kTestContent.size(), // end_position (exclusive)
+ kTestContent.size(), // content_length,
+ kTestContentType,
+ buffer,
+ base::Bind(&CopyResultFromUploadRangeCallbackAndQuit,
+ &response, &new_entry));
+
+ resume_operation->Start(
+ kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ // METHOD_PUT should be used to upload data.
+ EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
+ // Request should go to the upload URL.
+ EXPECT_EQ(upload_url.path(), http_request_.relative_url);
+ // Content-Range header should be added.
+ EXPECT_EQ("bytes 0-" +
+ base::Int64ToString(kTestContent.size() - 1) + "/" +
+ base::Int64ToString(kTestContent.size()),
+ http_request_.headers["Content-Range"]);
+ // The upload content should be set in the HTTP request.
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ(kTestContent, http_request_.content);
+
+ // Check the response.
+ EXPECT_EQ(HTTP_SUCCESS, response.code); // Because it's an existing file
+ // The start and end positions should be set to -1, if an upload is complete.
+ EXPECT_EQ(-1, response.start_position_received);
+ EXPECT_EQ(-1, response.end_position_received);
+}
+
+TEST_F(DriveApiOperationsTest, UploadExistingFileOperationWithETag) {
+ // Set an expected url for uploading.
+ expected_upload_path_ = kTestUploadExistingFilePath;
+
+ const char kTestContentType[] = "text/plain";
+ const std::string kTestContent(100, 'a');
+
+ GDataErrorCode error = GDATA_OTHER_ERROR;
+ GURL upload_url;
+
+ // Initiate uploading a new file to the directory with "parent_resource_id".
+ drive::InitiateUploadExistingFileOperation* operation =
+ new drive::InitiateUploadExistingFileOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ *url_generator_,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ kTestContentType,
+ kTestContent.size(),
+ "resource_id", // The resource id of the file to be overwritten.
+ kTestETag,
+ base::Bind(&test_util::CopyResultsFromInitiateUploadCallbackAndQuit,
+ &error, &upload_url));
+ operation->Start(kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(kTestUploadExistingFilePath, upload_url.path());
+ EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
+ EXPECT_EQ(base::Int64ToString(kTestContent.size()),
+ http_request_.headers["X-Upload-Content-Length"]);
+ EXPECT_EQ(kTestETag, http_request_.headers["If-Match"]);
+
+ EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
+ EXPECT_EQ("/upload/drive/v2/files/resource_id?uploadType=resumable",
+ http_request_.relative_url);
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_TRUE(http_request_.content.empty());
+
+ // 2) Upload the content to the upload URL.
+ scoped_refptr<net::IOBuffer> buffer = new net::StringIOBuffer(kTestContent);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> new_entry;
+
+ drive::ResumeUploadOperation* resume_operation =
+ new drive::ResumeUploadOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ UPLOAD_EXISTING_FILE,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ upload_url,
+ 0, // start_position
+ kTestContent.size(), // end_position (exclusive)
+ kTestContent.size(), // content_length,
+ kTestContentType,
+ buffer,
+ base::Bind(&CopyResultFromUploadRangeCallbackAndQuit,
+ &response, &new_entry));
+
+ resume_operation->Start(
+ kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ // METHOD_PUT should be used to upload data.
+ EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
+ // Request should go to the upload URL.
+ EXPECT_EQ(upload_url.path(), http_request_.relative_url);
+ // Content-Range header should be added.
+ EXPECT_EQ("bytes 0-" +
+ base::Int64ToString(kTestContent.size() - 1) + "/" +
+ base::Int64ToString(kTestContent.size()),
+ http_request_.headers["Content-Range"]);
+ // The upload content should be set in the HTTP request.
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_EQ(kTestContent, http_request_.content);
+
+ // Check the response.
+ EXPECT_EQ(HTTP_SUCCESS, response.code); // Because it's an existing file
+ // The start and end positions should be set to -1, if an upload is complete.
+ EXPECT_EQ(-1, response.start_position_received);
+ EXPECT_EQ(-1, response.end_position_received);
+}
+
+TEST_F(DriveApiOperationsTest, UploadExistingFileOperationWithETagConflicting) {
+ // Set an expected url for uploading.
+ expected_upload_path_ = kTestUploadExistingFilePath;
+
+ const char kTestContentType[] = "text/plain";
+ const std::string kTestContent(100, 'a');
+
+ GDataErrorCode error = GDATA_OTHER_ERROR;
+ GURL upload_url;
+
+ // Initiate uploading a new file to the directory with "parent_resource_id".
+ drive::InitiateUploadExistingFileOperation* operation =
+ new drive::InitiateUploadExistingFileOperation(
+ &operation_registry_,
+ request_context_getter_.get(),
+ *url_generator_,
+ base::FilePath(FILE_PATH_LITERAL("drive/file/path")),
+ kTestContentType,
+ kTestContent.size(),
+ "resource_id", // The resource id of the file to be overwritten.
+ "Conflicting-etag",
+ base::Bind(&test_util::CopyResultsFromInitiateUploadCallbackAndQuit,
+ &error, &upload_url));
+ operation->Start(kTestDriveApiAuthToken, kTestUserAgent,
+ base::Bind(&test_util::DoNothingForReAuthenticateCallback));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(HTTP_PRECONDITION, error);
+ EXPECT_EQ(kTestContentType, http_request_.headers["X-Upload-Content-Type"]);
+ EXPECT_EQ(base::Int64ToString(kTestContent.size()),
+ http_request_.headers["X-Upload-Content-Length"]);
+ EXPECT_EQ("Conflicting-etag", http_request_.headers["If-Match"]);
+
+ EXPECT_EQ(test_server::METHOD_PUT, http_request_.method);
+ EXPECT_EQ("/upload/drive/v2/files/resource_id?uploadType=resumable",
+ http_request_.relative_url);
+ EXPECT_TRUE(http_request_.has_content);
+ EXPECT_TRUE(http_request_.content.empty());
}
} // namespace google_apis
diff --git a/chrome/browser/google_apis/drive_api_service.cc b/chrome/browser/google_apis/drive_api_service.cc
index 1e058e5..c6a25d6 100644
--- a/chrome/browser/google_apis/drive_api_service.cc
+++ b/chrome/browser/google_apis/drive_api_service.cc
@@ -155,6 +155,34 @@ void ParseAppListAndRun(const google_apis::GetAppListCallback& callback,
callback.Run(error, app_list.Pass());
}
+// Parses the FileResource value to ResourceEntry for upload range operation,
+// and runs |callback| on the UI thread.
+void ParseResourceEntryForUploadRangeAndRun(
+ 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 =
+ ResourceEntry::CreateFromFileResource(*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());
+}
+
// 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";
@@ -509,8 +537,19 @@ void DriveAPIService::ResumeUpload(
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
- // TODO(kochi): Implement this.
- NOTREACHED();
+ runner_->StartOperationWithRetry(
+ new drive::ResumeUploadOperation(
+ operation_registry(),
+ url_request_context_getter_,
+ upload_mode,
+ drive_file_path,
+ upload_url,
+ start_position,
+ end_position,
+ content_length,
+ content_type,
+ buf,
+ base::Bind(&ParseResourceEntryForUploadRangeAndRun, callback)));
}
void DriveAPIService::GetUploadStatus(
diff --git a/chrome/browser/google_apis/gdata_wapi_operations_unittest.cc b/chrome/browser/google_apis/gdata_wapi_operations_unittest.cc
index 2041350..5f7c1a2 100644
--- a/chrome/browser/google_apis/gdata_wapi_operations_unittest.cc
+++ b/chrome/browser/google_apis/gdata_wapi_operations_unittest.cc
@@ -13,7 +13,6 @@
#include "base/message_loop_proxy.h"
#include "base/stringprintf.h"
#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_split.h"
#include "chrome/browser/google_apis/gdata_wapi_operations.h"
#include "chrome/browser/google_apis/gdata_wapi_parser.h"
#include "chrome/browser/google_apis/gdata_wapi_url_generator.h"
@@ -48,39 +47,6 @@ void CopyResultFromUploadRangeCallbackAndQuit(
MessageLoop::current()->Quit();
}
-// Parses a value of Content-Range header, which looks like
-// "bytes <start_position>-<end_position>/<length>".
-// Returns true on success.
-bool ParseContentRangeHeader(const std::string& value,
- int64* start_position,
- int64* end_position,
- int64* length) {
- DCHECK(start_position);
- DCHECK(end_position);
- DCHECK(length);
-
- std::string remaining;
- if (!test_util::RemovePrefix(value, "bytes ", &remaining))
- return false;
-
- std::vector<std::string> parts;
- base::SplitString(remaining, '/', &parts);
- if (parts.size() != 2U)
- return false;
-
- const std::string range = parts[0];
- if (!base::StringToInt64(parts[1], length))
- return false;
-
- parts.clear();
- base::SplitString(range, '-', &parts);
- if (parts.size() != 2U)
- return false;
-
- return (base::StringToInt64(parts[0], start_position) &&
- base::StringToInt64(parts[1], end_position));
-}
-
class GDataWapiOperationsTest : public testing::Test {
public:
GDataWapiOperationsTest()
@@ -304,10 +270,10 @@ class GDataWapiOperationsTest : public testing::Test {
int64 length = 0;
int64 start_position = 0;
int64 end_position = 0;
- if (!ParseContentRangeHeader(iter->second,
- &start_position,
- &end_position,
- &length)) {
+ if (!test_util::ParseContentRangeHeader(iter->second,
+ &start_position,
+ &end_position,
+ &length)) {
return scoped_ptr<test_server::HttpResponse>();
}
EXPECT_EQ(start_position, received_bytes_);
diff --git a/chrome/browser/google_apis/test_util.cc b/chrome/browser/google_apis/test_util.cc
index b76f24f..11feb40 100644
--- a/chrome/browser/google_apis/test_util.cc
+++ b/chrome/browser/google_apis/test_util.cc
@@ -12,6 +12,8 @@
#include "base/pending_task.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/google_apis/drive_api_parser.h"
#include "chrome/browser/google_apis/gdata_wapi_operations.h"
@@ -279,5 +281,35 @@ bool VerifyJsonData(const base::FilePath& expected_json_file_path,
return true;
}
+bool ParseContentRangeHeader(const std::string& value,
+ int64* start_position,
+ int64* end_position,
+ int64* length) {
+ DCHECK(start_position);
+ DCHECK(end_position);
+ DCHECK(length);
+
+ std::string remaining;
+ if (!RemovePrefix(value, "bytes ", &remaining))
+ return false;
+
+ std::vector<std::string> parts;
+ base::SplitString(remaining, '/', &parts);
+ if (parts.size() != 2U)
+ return false;
+
+ const std::string range = parts[0];
+ if (!base::StringToInt64(parts[1], length))
+ return false;
+
+ parts.clear();
+ base::SplitString(range, '-', &parts);
+ if (parts.size() != 2U)
+ return false;
+
+ return (base::StringToInt64(parts[0], start_position) &&
+ base::StringToInt64(parts[1], end_position));
+}
+
} // namespace test_util
} // namespace google_apis
diff --git a/chrome/browser/google_apis/test_util.h b/chrome/browser/google_apis/test_util.h
index fcd5b19..d482793 100644
--- a/chrome/browser/google_apis/test_util.h
+++ b/chrome/browser/google_apis/test_util.h
@@ -177,6 +177,14 @@ scoped_ptr<test_server::HttpResponse> HandleDownloadRequest(
bool VerifyJsonData(const base::FilePath& expected_json_file_path,
const base::Value* json_data);
+// Parses a value of Content-Range header, which looks like
+// "bytes <start_position>-<end_position>/<length>".
+// Returns true on success.
+bool ParseContentRangeHeader(const std::string& value,
+ int64* start_position,
+ int64* end_position,
+ int64* length);
+
} // namespace test_util
} // namespace google_apis
diff --git a/chrome/test/data/chromeos/drive/file_entry.json b/chrome/test/data/chromeos/drive/file_entry.json
new file mode 100644
index 0000000..3a1f71a
--- /dev/null
+++ b/chrome/test/data/chromeos/drive/file_entry.json
@@ -0,0 +1,49 @@
+{
+ "kind": "drive#file",
+ "id": "0B4v7G8yEYAWHUmRrU2lMS2hLABC",
+ "etag": "\"WtRjAPZWbDA7_fkFjc5ojsEvDEF/MTM0MzM2NzgwMDIXYZ\"",
+ "selfLink": "https://www.googleapis.com/drive/v2/files/0B4v7G8yEYAWHUmRrU2lMS2hLABC",
+ "webContentLink": "https://docs.google.com/uc?id=0B4v7G8yEYAWHUmRrU2lMS2hLABC&export=download",
+ "alternateLink": "https://docs.google.com/file/d/0B4v7G8yEYAWHUmRrU2lMS2hLABC/edit",
+ "title": "My first file data",
+ "mimeType": "application/octet-stream",
+ "labels": {
+ "starred": false,
+ "hidden": false,
+ "trashed": false,
+ "restricted": false,
+ "viewed": true
+ },
+ "createdDate": "2012-07-24T08:51:16.570Z",
+ "modifiedDate": "2012-07-27T05:43:20.269Z",
+ "modifiedByMeDate": "2012-07-27T05:43:20.269Z",
+ "lastViewedByMeDate": "2012-07-27T05:43:20.269Z",
+ "parents": [
+ {
+ "kind": "drive#parentReference",
+ "id": "0B4v7G8yEYAWHYW1OcExsUVZLABC",
+ "selfLink": "https://www.googleapis.com/drive/v2/files/0B4v7G8yEYAWHNGpYVHJINEFFABC/parents/0B4v7G8yEYAWHYW1OcExsUVZLABC",
+ "parentLink": "https://www.googleapis.com/drive/v2/files/0B4v7G8yEYAWHYW1OcExsUVZLABC",
+ "isRoot": false
+ }
+ ],
+ "downloadUrl": "https://www.example.com/download",
+ "userPermission": {
+ "kind": "drive#permission",
+ "etag": "\"WtRjAPZWbDA7_fkFjc5ojsEvDEF/LKOJhhwatz2OKj-OblM3EvO8XYZ\"",
+ "id": "me",
+ "role": "owner",
+ "type": "user"
+ },
+ "originalFilename": "Untitled Document.ext",
+ "fileExtension": "ext",
+ "md5Checksum": "d41d8cd98f00b204e9800998ecf8427e",
+ "fileSize": "1000",
+ "quotaBytesUsed": "1000",
+ "ownerNames": [
+ "Test User"
+ ],
+ "lastModifyingUserName": "Test User",
+ "editable": true,
+ "writersCanShare": true
+}