summaryrefslogtreecommitdiffstats
path: root/components/drive
diff options
context:
space:
mode:
authorlukasza <lukasza@chromium.org>2015-07-20 13:57:20 -0700
committerCommit bot <commit-bot@chromium.org>2015-07-20 20:58:23 +0000
commit8acc4eb18b6919f36413a10718a607f2a34fb8f6 (patch)
tree1e53fa500119384a12b38075ebcc97a469750dd7 /components/drive
parent1f37aaf390288b2864efe98a07427dbf91771489 (diff)
downloadchromium_src-8acc4eb18b6919f36413a10718a607f2a34fb8f6.zip
chromium_src-8acc4eb18b6919f36413a10718a607f2a34fb8f6.tar.gz
chromium_src-8acc4eb18b6919f36413a10718a607f2a34fb8f6.tar.bz2
Move (most of) chrome/browser/drive into components/drive.
Note that drive_notification_manager_factory is left behind in chrome/browser/drive. This is because it needs to stay dependent on the browser. This is okay, because drive_notification_manager_factory is not needed by drive::FileSystem and other parts of drive libraries that we want to componentize. Also note that some things moved to components/drive continue to have slightly undesirable dependencies: - Tests are still built and executed as part of the browser test suites. - drive_uploader.cc depends on content/public/browser/power_save_blocker.h This means that to use components/drive outside of the browser, users of components/drive have to provide a no-op implementation of power_save_blocker.h The most desirable approach in the long-term would be to componentize power_save_blocker.h. An alternative would be to continue with the state introduced by the current changelist (or to introduce drive-specific abstraction similar to power_save_blocker.h, but outside of the browser). - drive_api_util.cc depends on storage/browser/fileapi/file_stream_reader.h To get rid of this dependency, it is probably best to move FileStreamMd5Digester class closer to the only point of usage (c/b/chromeos/extensions/file_manager/private_api_file_system.cc). Landing via CQ with NOPRESUBMIT=true, because "git cl presubmit" incorrectly identifies target directory of a dependency (we are depending on third_party/cacheinvalidation/src/google/cacheinvalidation/types.proto and including the generated header via google/cacheinvalidation/types.pb.h). I believe this is a case of crbug.com/448570. All other presubmit checks are passing AFAIK: $ git cl presubmit Running presubmit commit checks ... ** Presubmit ERRORS ** Missing LGTM from OWNERS of dependencies added to DEPS: '+google/cacheinvalidation/types.pb.h', Presubmit checks took 2.9s to calculate. Test steps: 1. Verify that things still build via GYP (and unit tests pass). $ GYP_DEFINES="use_goma=1 gomadir=... chromeos=1" gclient sync $ ninja -C out/Debug -j 150 chrome unit_tests \ interactive_ui_tests browser_tests drive $ out/Debug/unit_tests 2. Verify that things still builds via GN. $ gn gen out/Default --args='target_os="chromeos" use_goma=true' $ ninja -C out/Default -j 150 chrome unit_tests \ interactive_ui_tests browser_tests components/drive TEST=Please see "Test steps" above. BUG=257943, 498951 NOPRESUBMIT=true Review URL: https://codereview.chromium.org/1190203002 Cr-Commit-Position: refs/heads/master@{#339512}
Diffstat (limited to 'components/drive')
-rw-r--r--components/drive/BUILD.gn57
-rw-r--r--components/drive/DEPS24
-rw-r--r--components/drive/OWNERS7
-rw-r--r--components/drive/README3
-rw-r--r--components/drive/drive_api_util.cc249
-rw-r--r--components/drive/drive_api_util.h124
-rw-r--r--components/drive/drive_api_util_unittest.cc110
-rw-r--r--components/drive/drive_app_registry.cc246
-rw-r--r--components/drive/drive_app_registry.h160
-rw-r--r--components/drive/drive_app_registry_observer.h21
-rw-r--r--components/drive/drive_app_registry_unittest.cc224
-rw-r--r--components/drive/drive_notification_manager.cc160
-rw-r--r--components/drive/drive_notification_manager.h98
-rw-r--r--components/drive/drive_notification_observer.h26
-rw-r--r--components/drive/drive_uploader.cc533
-rw-r--r--components/drive/drive_uploader.h240
-rw-r--r--components/drive/drive_uploader_unittest.cc955
-rw-r--r--components/drive/event_logger.cc63
-rw-r--r--components/drive/event_logger.h69
-rw-r--r--components/drive/event_logger_unittest.cc43
-rw-r--r--components/drive/service/drive_api_service.cc872
-rw-r--r--components/drive/service/drive_api_service.h269
-rw-r--r--components/drive/service/drive_api_service_unittest.cc79
-rw-r--r--components/drive/service/drive_service_interface.cc27
-rw-r--r--components/drive/service/drive_service_interface.h467
-rw-r--r--components/drive/service/dummy_drive_service.cc224
-rw-r--r--components/drive/service/dummy_drive_service.h166
-rw-r--r--components/drive/service/fake_drive_service.cc1787
-rw-r--r--components/drive/service/fake_drive_service.h406
-rw-r--r--components/drive/service/fake_drive_service_unittest.cc2177
-rw-r--r--components/drive/service/test_util.cc193
-rw-r--r--components/drive/service/test_util.h19
32 files changed, 10098 insertions, 0 deletions
diff --git a/components/drive/BUILD.gn b/components/drive/BUILD.gn
new file mode 100644
index 0000000..64f3141
--- /dev/null
+++ b/components/drive/BUILD.gn
@@ -0,0 +1,57 @@
+# Copyright (c) 2015 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.
+
+source_set("drive") {
+ sources = [
+ "drive_api_util.cc",
+ "drive_api_util.h",
+ "drive_app_registry.cc",
+ "drive_app_registry.h",
+ "drive_app_registry_observer.h",
+ "drive_notification_manager.cc",
+ "drive_notification_manager.h",
+ "drive_notification_observer.h",
+ "drive_uploader.cc",
+ "drive_uploader.h",
+ "event_logger.cc",
+ "event_logger.h",
+ "service/drive_api_service.cc",
+ "service/drive_api_service.h",
+ "service/drive_service_interface.cc",
+ "service/drive_service_interface.h",
+ ]
+ deps = [
+ "//base:base",
+ "//components/invalidation/public",
+
+ # TODO(lukasza): Remove this dependency (see DEPS file for more info).
+ "//content/public/browser:browser",
+
+ "//google_apis:google_apis",
+ "//net:net",
+
+ # TODO(lukasza): Remove this dependency (see DEPS file for more info).
+ "//storage/browser:browser",
+
+ "//third_party/re2:re2",
+ ]
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "service/dummy_drive_service.cc",
+ "service/dummy_drive_service.h",
+ "service/fake_drive_service.cc",
+ "service/fake_drive_service.h",
+ "service/test_util.cc",
+ "service/test_util.h",
+ ]
+ deps = [
+ ":drive",
+ "//base:base",
+ "//google_apis:google_apis",
+ "//net:net",
+ ]
+}
diff --git a/components/drive/DEPS b/components/drive/DEPS
new file mode 100644
index 0000000..818ed7c
--- /dev/null
+++ b/components/drive/DEPS
@@ -0,0 +1,24 @@
+include_rules = [
+ "+components/invalidation",
+ "+components/keyed_service",
+ "+google_apis",
+ "+google/cacheinvalidation/types.pb.h",
+ "+net",
+ "+third_party/re2",
+]
+
+specific_include_rules = {
+ # The following dependency should be removed to fully make this
+ # directory chrome/ and content/ and storage/ independent.
+ # crbug.com/257943
+ "drive_api_util\.cc": [
+ "+storage/browser/fileapi/file_stream_reader.h"
+ ],
+
+ # The following dependency should be removed to fully make this
+ # directory chrome/ and content/ and storage/ independent.
+ # crbug.com/257943
+ "drive_uploader\.cc": [
+ "+content/public/browser/power_save_blocker.h",
+ ],
+}
diff --git a/components/drive/OWNERS b/components/drive/OWNERS
new file mode 100644
index 0000000..aec41e6
--- /dev/null
+++ b/components/drive/OWNERS
@@ -0,0 +1,7 @@
+fukino@chromium.org
+hashimoto@chromium.org
+hidehiko@chromium.org
+hirono@chromium.org
+kinaba@chromium.org
+mtomasz@chromium.org
+yoshiki@chromium.org
diff --git a/components/drive/README b/components/drive/README
new file mode 100644
index 0000000..beaedae
--- /dev/null
+++ b/components/drive/README
@@ -0,0 +1,3 @@
+This directory contains utility code for accessing Google Drive, shared by
+SyncFileSystem (chrome/browser/sync_file_system) and Google Drive integration
+on Chrome OS (chrome/browser/chromeos/drive).
diff --git a/components/drive/drive_api_util.cc b/components/drive/drive_api_util.cc
new file mode 100644
index 0000000..57c3919
--- /dev/null
+++ b/components/drive/drive_api_util.cc
@@ -0,0 +1,249 @@
+// 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 "components/drive/drive_api_util.h"
+
+#include <string>
+
+#include "base/files/file.h"
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/cancellation_flag.h"
+#include "base/values.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "net/base/escape.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "storage/browser/fileapi/file_stream_reader.h"
+#include "third_party/re2/re2/re2.h"
+#include "url/gurl.h"
+
+namespace drive {
+namespace util {
+namespace {
+
+struct HostedDocumentKind {
+ const char* mime_type;
+ const char* extension;
+};
+
+const HostedDocumentKind kHostedDocumentKinds[] = {
+ {kGoogleDocumentMimeType, ".gdoc"},
+ {kGoogleSpreadsheetMimeType, ".gsheet"},
+ {kGooglePresentationMimeType, ".gslides"},
+ {kGoogleDrawingMimeType, ".gdraw"},
+ {kGoogleTableMimeType, ".gtable"},
+ {kGoogleFormMimeType, ".gform"},
+ {kGoogleMapMimeType, ".gmaps"},
+};
+
+const char kUnknownHostedDocumentExtension[] = ".glink";
+
+const int kMd5DigestBufferSize = 512 * 1024; // 512 kB.
+
+} // namespace
+
+std::string EscapeQueryStringValue(const std::string& str) {
+ std::string result;
+ result.reserve(str.size());
+ for (size_t i = 0; i < str.size(); ++i) {
+ if (str[i] == '\\' || str[i] == '\'') {
+ result.push_back('\\');
+ }
+ result.push_back(str[i]);
+ }
+ return result;
+}
+
+std::string TranslateQuery(const std::string& original_query) {
+ // In order to handle non-ascii white spaces correctly, convert to UTF16.
+ base::string16 query = base::UTF8ToUTF16(original_query);
+ const base::string16 kDelimiter(
+ base::kWhitespaceUTF16 + base::ASCIIToUTF16("\""));
+
+ std::string result;
+ for (size_t index = query.find_first_not_of(base::kWhitespaceUTF16);
+ index != base::string16::npos;
+ index = query.find_first_not_of(base::kWhitespaceUTF16, index)) {
+ bool is_exclusion = (query[index] == '-');
+ if (is_exclusion)
+ ++index;
+ if (index == query.length()) {
+ // Here, the token is '-' and it should be ignored.
+ continue;
+ }
+
+ size_t begin_token = index;
+ base::string16 token;
+ if (query[begin_token] == '"') {
+ // Quoted query.
+ ++begin_token;
+ size_t end_token = query.find('"', begin_token);
+ if (end_token == base::string16::npos) {
+ // This is kind of syntax error, since quoted string isn't finished.
+ // However, the query is built by user manually, so here we treat
+ // whole remaining string as a token as a fallback, by appending
+ // a missing double-quote character.
+ end_token = query.length();
+ query.push_back('"');
+ }
+
+ token = query.substr(begin_token, end_token - begin_token);
+ index = end_token + 1; // Consume last '"', too.
+ } else {
+ size_t end_token = query.find_first_of(kDelimiter, begin_token);
+ if (end_token == base::string16::npos) {
+ end_token = query.length();
+ }
+
+ token = query.substr(begin_token, end_token - begin_token);
+ index = end_token;
+ }
+
+ if (token.empty()) {
+ // Just ignore an empty token.
+ continue;
+ }
+
+ if (!result.empty()) {
+ // If there are two or more tokens, need to connect with "and".
+ result.append(" and ");
+ }
+
+ // The meaning of "fullText" should include title, description and content.
+ base::StringAppendF(
+ &result,
+ "%sfullText contains \'%s\'",
+ is_exclusion ? "not " : "",
+ EscapeQueryStringValue(base::UTF16ToUTF8(token)).c_str());
+ }
+
+ return result;
+}
+
+std::string CanonicalizeResourceId(const std::string& resource_id) {
+ // If resource ID is in the old WAPI format starting with a prefix like
+ // "document:", strip it and return the remaining part.
+ std::string stripped_resource_id;
+ if (RE2::FullMatch(resource_id, "^[a-z-]+(?::|%3A)([\\w-]+)$",
+ &stripped_resource_id))
+ return stripped_resource_id;
+ return resource_id;
+}
+
+std::string GetMd5Digest(const base::FilePath& file_path,
+ const base::CancellationFlag* cancellation_flag) {
+ base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!file.IsValid())
+ return std::string();
+
+ base::MD5Context context;
+ base::MD5Init(&context);
+
+ int64 offset = 0;
+ scoped_ptr<char[]> buffer(new char[kMd5DigestBufferSize]);
+ while (true) {
+ if (cancellation_flag && cancellation_flag->IsSet()) { // Cancelled.
+ return std::string();
+ }
+ int result = file.Read(offset, buffer.get(), kMd5DigestBufferSize);
+ if (result < 0) {
+ // Found an error.
+ return std::string();
+ }
+
+ if (result == 0) {
+ // End of file.
+ break;
+ }
+
+ offset += result;
+ base::MD5Update(&context, base::StringPiece(buffer.get(), result));
+ }
+
+ base::MD5Digest digest;
+ base::MD5Final(&digest, &context);
+ return base::MD5DigestToBase16(digest);
+}
+
+FileStreamMd5Digester::FileStreamMd5Digester()
+ : buffer_(new net::IOBuffer(kMd5DigestBufferSize)) {
+}
+
+FileStreamMd5Digester::~FileStreamMd5Digester() {
+}
+
+void FileStreamMd5Digester::GetMd5Digest(
+ scoped_ptr<storage::FileStreamReader> stream_reader,
+ const ResultCallback& callback) {
+ reader_ = stream_reader.Pass();
+ base::MD5Init(&md5_context_);
+
+ // Start the read/hash.
+ ReadNextChunk(callback);
+}
+
+void FileStreamMd5Digester::ReadNextChunk(const ResultCallback& callback) {
+ const int result =
+ reader_->Read(buffer_.get(), kMd5DigestBufferSize,
+ base::Bind(&FileStreamMd5Digester::OnChunkRead,
+ base::Unretained(this), callback));
+ if (result != net::ERR_IO_PENDING)
+ OnChunkRead(callback, result);
+}
+
+void FileStreamMd5Digester::OnChunkRead(const ResultCallback& callback,
+ int bytes_read) {
+ if (bytes_read < 0) {
+ // Error - just return empty string.
+ callback.Run("");
+ return;
+ } else if (bytes_read == 0) {
+ // EOF.
+ base::MD5Digest digest;
+ base::MD5Final(&digest, &md5_context_);
+ std::string result = base::MD5DigestToBase16(digest);
+ callback.Run(result);
+ return;
+ }
+
+ // Read data and digest it.
+ base::MD5Update(&md5_context_,
+ base::StringPiece(buffer_->data(), bytes_read));
+
+ // Kick off the next read.
+ ReadNextChunk(callback);
+}
+
+std::string GetHostedDocumentExtension(const std::string& mime_type) {
+ for (size_t i = 0; i < arraysize(kHostedDocumentKinds); ++i) {
+ if (mime_type == kHostedDocumentKinds[i].mime_type)
+ return kHostedDocumentKinds[i].extension;
+ }
+ return kUnknownHostedDocumentExtension;
+}
+
+bool IsKnownHostedDocumentMimeType(const std::string& mime_type) {
+ for (size_t i = 0; i < arraysize(kHostedDocumentKinds); ++i) {
+ if (mime_type == kHostedDocumentKinds[i].mime_type)
+ return true;
+ }
+ return false;
+}
+
+bool HasHostedDocumentExtension(const base::FilePath& path) {
+ const std::string extension = base::FilePath(path.Extension()).AsUTF8Unsafe();
+ for (size_t i = 0; i < arraysize(kHostedDocumentKinds); ++i) {
+ if (extension == kHostedDocumentKinds[i].extension)
+ return true;
+ }
+ return extension == kUnknownHostedDocumentExtension;
+}
+
+} // namespace util
+} // namespace drive
diff --git a/components/drive/drive_api_util.h b/components/drive/drive_api_util.h
new file mode 100644
index 0000000..f34f21e
--- /dev/null
+++ b/components/drive/drive_api_util.h
@@ -0,0 +1,124 @@
+// 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.
+
+#ifndef COMPONENTS_DRIVE_DRIVE_API_UTIL_H_
+#define COMPONENTS_DRIVE_DRIVE_API_UTIL_H_
+
+#include <string>
+
+#include "base/md5.h"
+#include "base/memory/scoped_ptr.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+#include "google_apis/drive/drive_common_callbacks.h"
+
+class GURL;
+
+namespace base {
+class CancellationFlag;
+class FilePath;
+class Value;
+} // namespace base
+
+namespace google_apis {
+class ChangeList;
+class ChangeResource;
+class FileList;
+class FileResource;
+class ResourceEntry;
+} // namespace google_apis
+
+namespace net {
+class IOBuffer;
+} // namespace net
+
+namespace storage {
+class FileStreamReader;
+} // namespace storage
+
+namespace drive {
+namespace util {
+
+// Google Apps MIME types:
+const char kGoogleDocumentMimeType[] = "application/vnd.google-apps.document";
+const char kGoogleDrawingMimeType[] = "application/vnd.google-apps.drawing";
+const char kGooglePresentationMimeType[] =
+ "application/vnd.google-apps.presentation";
+const char kGoogleSpreadsheetMimeType[] =
+ "application/vnd.google-apps.spreadsheet";
+const char kGoogleTableMimeType[] = "application/vnd.google-apps.table";
+const char kGoogleFormMimeType[] = "application/vnd.google-apps.form";
+const char kGoogleMapMimeType[] = "application/vnd.google-apps.map";
+const char kDriveFolderMimeType[] = "application/vnd.google-apps.folder";
+
+// Escapes ' to \' in the |str|. This is designed to use for string value of
+// search parameter on Drive API v2.
+// See also: https://developers.google.com/drive/search-parameters
+std::string EscapeQueryStringValue(const std::string& str);
+
+// Parses the query, and builds a search query for Drive API v2.
+// This only supports:
+// Regular query (e.g. dog => fullText contains 'dog')
+// Conjunctions
+// (e.g. dog cat => fullText contains 'dog' and fullText contains 'cat')
+// Exclusion query (e.g. -cat => not fullText contains 'cat').
+// Quoted query (e.g. "dog cat" => fullText contains 'dog cat').
+// See also: https://developers.google.com/drive/search-parameters
+std::string TranslateQuery(const std::string& original_query);
+
+// If |resource_id| is in the old resource ID format used by WAPI, converts it
+// into the new format.
+std::string CanonicalizeResourceId(const std::string& resource_id);
+
+// Returns the (base-16 encoded) MD5 digest of the file content at |file_path|,
+// or an empty string if an error is found.
+std::string GetMd5Digest(const base::FilePath& file_path,
+ const base::CancellationFlag* cancellation_flag);
+
+// Computes the (base-16 encoded) MD5 digest of data extracted from a file
+// stream.
+class FileStreamMd5Digester {
+ public:
+ typedef base::Callback<void(const std::string&)> ResultCallback;
+
+ FileStreamMd5Digester();
+ ~FileStreamMd5Digester();
+
+ // Computes an MD5 digest of data read from the given |streamReader|. The
+ // work occurs asynchronously, and the resulting hash is returned via the
+ // |callback|. If an error occurs, |callback| is called with an empty string.
+ // Only one stream can be processed at a time by each digester. Do not call
+ // GetMd5Digest before the results of a previous call have been returned.
+ void GetMd5Digest(scoped_ptr<storage::FileStreamReader> stream_reader,
+ const ResultCallback& callback);
+
+ private:
+ // Kicks off a read of the next chunk from the stream.
+ void ReadNextChunk(const ResultCallback& callback);
+ // Handles the incoming chunk of data from a stream read.
+ void OnChunkRead(const ResultCallback& callback, int bytes_read);
+
+ // Maximum chunk size for read operations.
+ scoped_ptr<storage::FileStreamReader> reader_;
+ scoped_refptr<net::IOBuffer> buffer_;
+ base::MD5Context md5_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileStreamMd5Digester);
+};
+
+// Returns preferred file extension for hosted documents which have given mime
+// type.
+std::string GetHostedDocumentExtension(const std::string& mime_type);
+
+// Returns true if the given mime type is corresponding to one of known hosted
+// document types.
+bool IsKnownHostedDocumentMimeType(const std::string& mime_type);
+
+// Returns true if the given file path has an extension corresponding to one of
+// hosted document types.
+bool HasHostedDocumentExtension(const base::FilePath& path);
+
+} // namespace util
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DRIVE_API_UTIL_H_
diff --git a/components/drive/drive_api_util_unittest.cc b/components/drive/drive_api_util_unittest.cc
new file mode 100644
index 0000000..20fe8a5
--- /dev/null
+++ b/components/drive/drive_api_util_unittest.cc
@@ -0,0 +1,110 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/drive_api_util.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/md5.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace drive {
+namespace util {
+
+TEST(DriveApiUtilTest, EscapeQueryStringValue) {
+ EXPECT_EQ("abcde", EscapeQueryStringValue("abcde"));
+ EXPECT_EQ("\\'", EscapeQueryStringValue("'"));
+ EXPECT_EQ("\\'abcde\\'", EscapeQueryStringValue("'abcde'"));
+ EXPECT_EQ("\\\\", EscapeQueryStringValue("\\"));
+ EXPECT_EQ("\\\\\\'", EscapeQueryStringValue("\\'"));
+}
+
+TEST(DriveApiUtilTest, TranslateQuery) {
+ EXPECT_EQ("", TranslateQuery(""));
+ EXPECT_EQ("fullText contains 'dog'", TranslateQuery("dog"));
+ EXPECT_EQ("fullText contains 'dog' and fullText contains 'cat'",
+ TranslateQuery("dog cat"));
+ EXPECT_EQ("not fullText contains 'cat'", TranslateQuery("-cat"));
+ EXPECT_EQ("fullText contains 'dog cat'", TranslateQuery("\"dog cat\""));
+
+ // Should handles full-width white space correctly.
+ // Note: \xE3\x80\x80 (\u3000) is Ideographic Space (a.k.a. Japanese
+ // full-width whitespace).
+ EXPECT_EQ("fullText contains 'dog' and fullText contains 'cat'",
+ TranslateQuery("dog" "\xE3\x80\x80" "cat"));
+
+ // If the quoted token is not closed (i.e. the last '"' is missing),
+ // we handle the remaining string is one token, as a fallback.
+ EXPECT_EQ("fullText contains 'dog cat'", TranslateQuery("\"dog cat"));
+
+ // For quoted text with leading '-'.
+ EXPECT_EQ("not fullText contains 'dog cat'", TranslateQuery("-\"dog cat\""));
+
+ // Empty tokens should be simply ignored.
+ EXPECT_EQ("", TranslateQuery("-"));
+ EXPECT_EQ("", TranslateQuery("\"\""));
+ EXPECT_EQ("", TranslateQuery("-\"\""));
+ EXPECT_EQ("", TranslateQuery("\"\"\"\""));
+ EXPECT_EQ("", TranslateQuery("\"\" \"\""));
+ EXPECT_EQ("fullText contains 'dog'", TranslateQuery("\"\" dog \"\""));
+}
+
+TEST(DriveAPIUtilTest, CanonicalizeResourceId) {
+ std::string resource_id("1YsCnrMxxgp7LDdtlFDt-WdtEIth89vA9inrILtvK-Ug");
+
+ // New style ID is unchanged.
+ EXPECT_EQ(resource_id, CanonicalizeResourceId(resource_id));
+
+ // Drop prefixes from old style IDs.
+ EXPECT_EQ(resource_id, CanonicalizeResourceId("document:" + resource_id));
+ EXPECT_EQ(resource_id, CanonicalizeResourceId("spreadsheet:" + resource_id));
+ EXPECT_EQ(resource_id, CanonicalizeResourceId("presentation:" + resource_id));
+ EXPECT_EQ(resource_id, CanonicalizeResourceId("drawing:" + resource_id));
+ EXPECT_EQ(resource_id, CanonicalizeResourceId("table:" + resource_id));
+ EXPECT_EQ(resource_id, CanonicalizeResourceId("externalapp:" + resource_id));
+}
+
+TEST(DriveAPIUtilTest, GetMd5Digest) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ base::FilePath path = temp_dir.path().AppendASCII("test.txt");
+ const char kTestData[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ ASSERT_TRUE(google_apis::test_util::WriteStringToFile(path, kTestData));
+
+ EXPECT_EQ(base::MD5String(kTestData), GetMd5Digest(path, nullptr));
+}
+
+TEST(DriveAPIUtilTest, HasHostedDocumentExtension) {
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gdoc")));
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gsheet")));
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gslides")));
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gdraw")));
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gtable")));
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gform")));
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gmaps")));
+ EXPECT_TRUE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.glink")));
+
+ EXPECT_FALSE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gdocs")));
+ EXPECT_FALSE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.docx")));
+ EXPECT_FALSE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.jpg")));
+ EXPECT_FALSE(
+ HasHostedDocumentExtension(base::FilePath::FromUTF8Unsafe("xx.gmap")));
+}
+
+} // namespace util
+} // namespace drive
diff --git a/components/drive/drive_app_registry.cc b/components/drive/drive_app_registry.cc
new file mode 100644
index 0000000..90598e7
--- /dev/null
+++ b/components/drive/drive_app_registry.cc
@@ -0,0 +1,246 @@
+// Copyright 2014 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 "components/drive/drive_app_registry.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "components/drive/drive_app_registry_observer.h"
+#include "components/drive/service/drive_service_interface.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/google_api_keys.h"
+
+namespace {
+
+// Add {selector -> app_id} mapping to |map|.
+void AddAppSelectorList(const ScopedVector<std::string>& selectors,
+ const std::string& app_id,
+ std::multimap<std::string, std::string>* map) {
+ for (size_t i = 0; i < selectors.size(); ++i)
+ map->insert(std::make_pair(*selectors[i], app_id));
+}
+
+// Append list of app ids in |map| looked up by |selector| to |matched_apps|.
+void FindAppsForSelector(const std::string& selector,
+ const std::multimap<std::string, std::string>& map,
+ std::vector<std::string>* matched_apps) {
+ typedef std::multimap<std::string, std::string>::const_iterator iterator;
+ std::pair<iterator, iterator> range = map.equal_range(selector);
+ for (iterator it = range.first; it != range.second; ++it)
+ matched_apps->push_back(it->second);
+}
+
+void RemoveAppFromSelector(const std::string& app_id,
+ std::multimap<std::string, std::string>* map) {
+ typedef std::multimap<std::string, std::string>::iterator iterator;
+ for (iterator it = map->begin(); it != map->end(); ) {
+ iterator now = it++;
+ if (now->second == app_id)
+ map->erase(now);
+ }
+}
+
+} // namespace
+
+namespace drive {
+
+DriveAppInfo::DriveAppInfo() {
+}
+
+DriveAppInfo::DriveAppInfo(
+ const std::string& app_id,
+ const std::string& product_id,
+ const IconList& app_icons,
+ const IconList& document_icons,
+ const std::string& app_name,
+ const GURL& create_url,
+ bool is_removable)
+ : app_id(app_id),
+ product_id(product_id),
+ app_icons(app_icons),
+ document_icons(document_icons),
+ app_name(app_name),
+ create_url(create_url),
+ is_removable(is_removable) {
+}
+
+DriveAppInfo::~DriveAppInfo() {
+}
+
+DriveAppRegistry::DriveAppRegistry(DriveServiceInterface* drive_service)
+ : drive_service_(drive_service),
+ is_updating_(false),
+ weak_ptr_factory_(this) {
+}
+
+DriveAppRegistry::~DriveAppRegistry() {
+}
+
+void DriveAppRegistry::GetAppsForFile(
+ const base::FilePath::StringType& file_extension,
+ const std::string& mime_type,
+ std::vector<DriveAppInfo>* apps) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::vector<std::string> matched_apps;
+ if (!file_extension.empty()) {
+ const std::string without_dot =
+ base::FilePath(file_extension.substr(1)).AsUTF8Unsafe();
+ FindAppsForSelector(without_dot, extension_map_, &matched_apps);
+ }
+ if (!mime_type.empty())
+ FindAppsForSelector(mime_type, mimetype_map_, &matched_apps);
+
+ // Insert found Drive apps into |apps|, but skip duplicate results.
+ std::set<std::string> inserted_app_ids;
+ for (size_t i = 0; i < matched_apps.size(); ++i) {
+ if (inserted_app_ids.count(matched_apps[i]) == 0) {
+ inserted_app_ids.insert(matched_apps[i]);
+ std::map<std::string, DriveAppInfo>::const_iterator it =
+ all_apps_.find(matched_apps[i]);
+ DCHECK(it != all_apps_.end());
+ apps->push_back(it->second);
+ }
+ }
+}
+
+void DriveAppRegistry::GetAppList(std::vector<DriveAppInfo>* apps) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ apps->clear();
+ for (std::map<std::string, DriveAppInfo>::const_iterator
+ it = all_apps_.begin(); it != all_apps_.end(); ++it) {
+ apps->push_back(it->second);
+ }
+}
+
+void DriveAppRegistry::Update() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (is_updating_) // There is already an update in progress.
+ return;
+ is_updating_ = true;
+
+ drive_service_->GetAppList(
+ base::Bind(&DriveAppRegistry::UpdateAfterGetAppList,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DriveAppRegistry::UpdateAfterGetAppList(
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::AppList> app_list) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK(is_updating_);
+ is_updating_ = false;
+
+ // Failed to fetch the data from the server. We can do nothing here.
+ if (gdata_error != google_apis::HTTP_SUCCESS)
+ return;
+
+ DCHECK(app_list);
+ UpdateFromAppList(*app_list);
+}
+
+void DriveAppRegistry::UpdateFromAppList(const google_apis::AppList& app_list) {
+ all_apps_.clear();
+ extension_map_.clear();
+ mimetype_map_.clear();
+
+ for (size_t i = 0; i < app_list.items().size(); ++i) {
+ const google_apis::AppResource& app = *app_list.items()[i];
+ const std::string id = app.application_id();
+
+ DriveAppInfo::IconList app_icons;
+ DriveAppInfo::IconList document_icons;
+ for (size_t j = 0; j < app.icons().size(); ++j) {
+ const google_apis::DriveAppIcon& icon = *app.icons()[j];
+ if (icon.icon_url().is_empty())
+ continue;
+ if (icon.category() == google_apis::DriveAppIcon::APPLICATION)
+ app_icons.push_back(std::make_pair(icon.icon_side_length(),
+ icon.icon_url()));
+ if (icon.category() == google_apis::DriveAppIcon::DOCUMENT)
+ document_icons.push_back(std::make_pair(icon.icon_side_length(),
+ icon.icon_url()));
+ }
+
+ all_apps_[id] = DriveAppInfo(app.application_id(),
+ app.product_id(),
+ app_icons,
+ document_icons,
+ app.name(),
+ app.create_url(),
+ app.is_removable());
+
+ // TODO(kinaba): consider taking primary/secondary distinction into account.
+ AddAppSelectorList(app.primary_mimetypes(), id, &mimetype_map_);
+ AddAppSelectorList(app.secondary_mimetypes(), id, &mimetype_map_);
+ AddAppSelectorList(app.primary_file_extensions(), id, &extension_map_);
+ AddAppSelectorList(app.secondary_file_extensions(), id, &extension_map_);
+ }
+
+ FOR_EACH_OBSERVER(DriveAppRegistryObserver,
+ observers_,
+ OnDriveAppRegistryUpdated());
+}
+
+void DriveAppRegistry::AddObserver(DriveAppRegistryObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DriveAppRegistry::RemoveObserver(DriveAppRegistryObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void DriveAppRegistry::UninstallApp(const std::string& app_id,
+ const UninstallCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ drive_service_->UninstallApp(app_id,
+ base::Bind(&DriveAppRegistry::OnAppUninstalled,
+ weak_ptr_factory_.GetWeakPtr(),
+ app_id,
+ callback));
+}
+
+void DriveAppRegistry::OnAppUninstalled(const std::string& app_id,
+ const UninstallCallback& callback,
+ google_apis::DriveApiErrorCode error) {
+ if (error == google_apis::HTTP_NO_CONTENT) {
+ all_apps_.erase(app_id);
+ RemoveAppFromSelector(app_id, &mimetype_map_);
+ RemoveAppFromSelector(app_id, &extension_map_);
+ }
+ callback.Run(error);
+}
+
+// static
+bool DriveAppRegistry::IsAppUninstallSupported() {
+ return google_apis::IsGoogleChromeAPIKeyUsed();
+}
+
+namespace util {
+
+GURL FindPreferredIcon(const DriveAppInfo::IconList& icons,
+ int preferred_size) {
+ if (icons.empty())
+ return GURL();
+
+ DriveAppInfo::IconList sorted_icons = icons;
+ std::sort(sorted_icons.rbegin(), sorted_icons.rend());
+
+ // Go forward while the size is larger or equal to preferred_size.
+ size_t i = 1;
+ while (i < sorted_icons.size() && sorted_icons[i].first >= preferred_size)
+ ++i;
+ return sorted_icons[i - 1].second;
+}
+
+} // namespace util
+} // namespace drive
diff --git a/components/drive/drive_app_registry.h b/components/drive/drive_app_registry.h
new file mode 100644
index 0000000..817543f
--- /dev/null
+++ b/components/drive/drive_app_registry.h
@@ -0,0 +1,160 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_DRIVE_DRIVE_APP_REGISTRY_H_
+#define COMPONENTS_DRIVE_DRIVE_APP_REGISTRY_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+#include "url/gurl.h"
+
+namespace google_apis {
+class AppList;
+} // namespace google_apis
+
+namespace drive {
+
+class DriveAppRegistryObserver;
+class DriveServiceInterface;
+
+// Data structure that defines Drive app. See
+// https://chrome.google.com/webstore/category/collection/drive_apps for
+// Drive apps available on the webstore.
+struct DriveAppInfo {
+ typedef std::vector<std::pair<int, GURL> > IconList;
+
+ DriveAppInfo();
+ DriveAppInfo(const std::string& app_id,
+ const std::string& product_id,
+ const IconList& app_icons,
+ const IconList& document_icons,
+ const std::string& app_name,
+ const GURL& create_url,
+ bool is_removable);
+ ~DriveAppInfo();
+
+ // Drive app id.
+ std::string app_id;
+ // Drive app's product id. This is different from app id that is used inside
+ // Drive. Product id is an id for the app in webstore; hence, it can be used
+ // for identifying the same app install as Chrome extension and as Drive app
+ // at the same time.
+ std::string product_id;
+ // Drive application icon URLs for this app, paired with their size (length of
+ // a side in pixels).
+ IconList app_icons;
+ // Drive document icon URLs for this app, paired with their size (length of
+ // a side in pixels).
+ IconList document_icons;
+ // App name.
+ std::string app_name;
+ // URL for opening a new file in the app. Empty if the app does not support
+ // new file creation.
+ GURL create_url;
+ // Returns if UninstallApp() is allowed for the app. Built-in apps have this
+ // field set false.
+ bool is_removable;
+};
+
+// Callback type for UninstallApp().
+typedef base::Callback<void(google_apis::DriveApiErrorCode)> UninstallCallback;
+
+// Keeps the track of installed drive applications in-memory.
+class DriveAppRegistry {
+ public:
+ explicit DriveAppRegistry(DriveServiceInterface* scheduler);
+ ~DriveAppRegistry();
+
+ // Returns a list of Drive app information for the |file_extension| with
+ // |mime_type|.
+ void GetAppsForFile(const base::FilePath::StringType& file_extension,
+ const std::string& mime_type,
+ std::vector<DriveAppInfo>* apps) const;
+
+ // Returns the list of all Drive apps installed.
+ void GetAppList(std::vector<DriveAppInfo>* apps) const;
+
+ // Uninstalls the app specified by |app_id|. This method sends requests to the
+ // remote server, and returns the result to |callback| asynchronously. When
+ // succeeded, the callback receives HTTP_NO_CONTENT, and error code otherwise.
+ // |callback| must not be null.
+ void UninstallApp(const std::string& app_id,
+ const UninstallCallback& callback);
+
+ // Checks whether UinstallApp is supported. The feature is available only for
+ // clients with whitelisted API keys (like Official Google Chrome build).
+ static bool IsAppUninstallSupported();
+
+ // Updates this registry by fetching the data from the server.
+ void Update();
+
+ // Updates this registry from the |app_list|.
+ void UpdateFromAppList(const google_apis::AppList& app_list);
+
+ void AddObserver(DriveAppRegistryObserver* observer);
+ void RemoveObserver(DriveAppRegistryObserver* observer);
+
+ private:
+ // Part of Update(). Runs upon the completion of fetching the Drive apps
+ // data from the server.
+ void UpdateAfterGetAppList(google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::AppList> app_list);
+
+ // Part of UninstallApp(). Receives the response from the server.
+ void OnAppUninstalled(const std::string& app_id,
+ const UninstallCallback& callback,
+ google_apis::DriveApiErrorCode error);
+
+ // The class is expected to run on UI thread.
+ base::ThreadChecker thread_checker_;
+
+ // Map of application id to each app's info.
+ std::map<std::string, DriveAppInfo> all_apps_;
+
+ // Defines mapping between file content type selectors (extensions, MIME
+ // types) and corresponding app.
+ typedef std::multimap<std::string, std::string> DriveAppFileSelectorMap;
+ DriveAppFileSelectorMap extension_map_;
+ DriveAppFileSelectorMap mimetype_map_;
+
+ DriveServiceInterface* drive_service_;
+
+ bool is_updating_;
+
+ base::ObserverList<DriveAppRegistryObserver> observers_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<DriveAppRegistry> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveAppRegistry);
+};
+
+namespace util {
+
+// The preferred icon size, which should usually be used for FindPreferredIcon;
+const int kPreferredIconSize = 16;
+
+// Finds an icon in the list of icons. If unable to find an icon of the exact
+// size requested, returns one with the next larger size. If all icons are
+// smaller than the preferred size, we'll return the largest one available.
+// Icons do not have to be sorted by the icon size. If there are no icons in
+// the list, returns an empty URL.
+GURL FindPreferredIcon(const DriveAppInfo::IconList& icons,
+ int preferred_size);
+
+} // namespace util
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DRIVE_APP_REGISTRY_H_
diff --git a/components/drive/drive_app_registry_observer.h b/components/drive/drive_app_registry_observer.h
new file mode 100644
index 0000000..d22781f
--- /dev/null
+++ b/components/drive/drive_app_registry_observer.h
@@ -0,0 +1,21 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_DRIVE_DRIVE_APP_REGISTRY_OBSERVER_H_
+#define COMPONENTS_DRIVE_DRIVE_APP_REGISTRY_OBSERVER_H_
+
+namespace drive {
+
+class DriveAppRegistryObserver {
+ public:
+ // Invoked when DriveAppRegistry has updated its list of Drive apps.
+ virtual void OnDriveAppRegistryUpdated() = 0;
+
+ protected:
+ virtual ~DriveAppRegistryObserver() {}
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DRIVE_APP_REGISTRY_OBSERVER_H_
diff --git a/components/drive/drive_app_registry_unittest.cc b/components/drive/drive_app_registry_unittest.cc
new file mode 100644
index 0000000..d5dfd6c
--- /dev/null
+++ b/components/drive/drive_app_registry_unittest.cc
@@ -0,0 +1,224 @@
+// Copyright 2014 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 "components/drive/drive_app_registry.h"
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/drive/drive_app_registry_observer.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+
+class TestDriveAppRegistryObserver : public DriveAppRegistryObserver {
+ public:
+ explicit TestDriveAppRegistryObserver(DriveAppRegistry* registry)
+ : registry_(registry),
+ update_count_(0) {
+ registry_->AddObserver(this);
+ }
+ ~TestDriveAppRegistryObserver() override { registry_->RemoveObserver(this); }
+
+ int update_count() const { return update_count_; }
+
+ private:
+ // DriveAppRegistryObserver overrides:
+ void OnDriveAppRegistryUpdated() override { ++update_count_; }
+
+ DriveAppRegistry* registry_;
+ int update_count_;
+ DISALLOW_COPY_AND_ASSIGN(TestDriveAppRegistryObserver);
+};
+
+class DriveAppRegistryTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ fake_drive_service_.reset(new FakeDriveService);
+ fake_drive_service_->LoadAppListForDriveApi("drive/applist.json");
+
+ apps_registry_.reset(new DriveAppRegistry(fake_drive_service_.get()));
+ }
+
+ bool VerifyApp(const std::vector<DriveAppInfo>& list,
+ const std::string& app_id,
+ const std::string& app_name) {
+ bool found = false;
+ for (size_t i = 0; i < list.size(); ++i) {
+ const DriveAppInfo& app = list[i];
+ if (app_id == app.app_id) {
+ EXPECT_EQ(app_name, app.app_name);
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found) << "Unable to find app with app_id " << app_id;
+ return found;
+ }
+
+ base::MessageLoop message_loop_;
+ scoped_ptr<FakeDriveService> fake_drive_service_;
+ scoped_ptr<DriveAppRegistry> apps_registry_;
+};
+
+TEST_F(DriveAppRegistryTest, BasicParse) {
+ TestDriveAppRegistryObserver observer(apps_registry_.get());
+
+ apps_registry_->Update();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, observer.update_count());
+
+ std::vector<DriveAppInfo> apps;
+ apps_registry_->GetAppList(&apps);
+
+ ASSERT_EQ(2u, apps.size());
+ EXPECT_EQ("123456788192", apps[0].app_id);
+ EXPECT_EQ("Drive app 1", apps[0].app_name);
+ EXPECT_EQ("https://www.example.com/createForApp1",
+ apps[0].create_url.spec());
+ EXPECT_EQ("abcdefghabcdefghabcdefghabcdefgh", apps[0].product_id);
+ EXPECT_TRUE(apps[0].is_removable);
+}
+
+TEST_F(DriveAppRegistryTest, LoadAndFindDriveApps) {
+ TestDriveAppRegistryObserver observer(apps_registry_.get());
+
+ apps_registry_->Update();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, observer.update_count());
+
+ // Find by primary extension 'exe'.
+ std::vector<DriveAppInfo> ext_results;
+ base::FilePath ext_file(FILE_PATH_LITERAL("drive/file.exe"));
+ apps_registry_->GetAppsForFile(ext_file.Extension(), "", &ext_results);
+ ASSERT_EQ(1U, ext_results.size());
+ VerifyApp(ext_results, "123456788192", "Drive app 1");
+
+ // Find by primary MIME type.
+ std::vector<DriveAppInfo> primary_app;
+ apps_registry_->GetAppsForFile(base::FilePath::StringType(),
+ "application/vnd.google-apps.drive-sdk.123456788192", &primary_app);
+ ASSERT_EQ(1U, primary_app.size());
+ VerifyApp(primary_app, "123456788192", "Drive app 1");
+
+ // Find by secondary MIME type.
+ std::vector<DriveAppInfo> secondary_app;
+ apps_registry_->GetAppsForFile(
+ base::FilePath::StringType(), "text/html", &secondary_app);
+ ASSERT_EQ(1U, secondary_app.size());
+ VerifyApp(secondary_app, "123456788192", "Drive app 1");
+}
+
+TEST_F(DriveAppRegistryTest, UpdateFromAppList) {
+ scoped_ptr<base::Value> app_info_value =
+ google_apis::test_util::LoadJSONFile("drive/applist.json");
+ scoped_ptr<google_apis::AppList> app_list(
+ google_apis::AppList::CreateFrom(*app_info_value));
+
+ TestDriveAppRegistryObserver observer(apps_registry_.get());
+ apps_registry_->UpdateFromAppList(*app_list);
+ EXPECT_EQ(1, observer.update_count());
+
+ // Confirm that something was loaded from applist.json.
+ std::vector<DriveAppInfo> ext_results;
+ base::FilePath ext_file(FILE_PATH_LITERAL("drive/file.exe"));
+ apps_registry_->GetAppsForFile(ext_file.Extension(), "", &ext_results);
+ ASSERT_EQ(1U, ext_results.size());
+}
+
+TEST_F(DriveAppRegistryTest, MultipleUpdate) {
+ TestDriveAppRegistryObserver observer(apps_registry_.get());
+
+ // Call Update().
+ apps_registry_->Update();
+ EXPECT_EQ(0, observer.update_count());
+
+ // Call Update() again.
+ // This call should be ignored because there is already an ongoing update.
+ apps_registry_->Update();
+ EXPECT_EQ(0, observer.update_count());
+
+ // The app list should be loaded only once.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, fake_drive_service_->app_list_load_count());
+ EXPECT_EQ(1, observer.update_count());
+}
+
+TEST(DriveAppRegistryUtilTest, FindPreferredIcon_Empty) {
+ DriveAppInfo::IconList icons;
+ EXPECT_EQ("",
+ util::FindPreferredIcon(icons, util::kPreferredIconSize).spec());
+}
+
+TEST(DriveAppRegistryUtilTest, FindPreferredIcon_) {
+ const char kSmallerIconUrl[] = "http://example.com/smaller.png";
+ const char kMediumIconUrl[] = "http://example.com/medium.png";
+ const char kBiggerIconUrl[] = "http://example.com/bigger.png";
+ const int kMediumSize = 16;
+
+ DriveAppInfo::IconList icons;
+ // The icons are not sorted by the size.
+ icons.push_back(std::make_pair(kMediumSize,
+ GURL(kMediumIconUrl)));
+ icons.push_back(std::make_pair(kMediumSize + 2,
+ GURL(kBiggerIconUrl)));
+ icons.push_back(std::make_pair(kMediumSize - 2,
+ GURL(kSmallerIconUrl)));
+
+ // Exact match.
+ EXPECT_EQ(kMediumIconUrl,
+ util::FindPreferredIcon(icons, kMediumSize).spec());
+ // The requested size is in-between of smaller.png and
+ // medium.png. medium.png should be returned.
+ EXPECT_EQ(kMediumIconUrl,
+ util::FindPreferredIcon(icons, kMediumSize - 1).spec());
+ // The requested size is smaller than the smallest icon. The smallest icon
+ // should be returned.
+ EXPECT_EQ(kSmallerIconUrl,
+ util::FindPreferredIcon(icons, kMediumSize - 3).spec());
+ // The requested size is larger than the largest icon. The largest icon
+ // should be returned.
+ EXPECT_EQ(kBiggerIconUrl,
+ util::FindPreferredIcon(icons, kMediumSize + 3).spec());
+}
+
+TEST_F(DriveAppRegistryTest, UninstallDriveApp) {
+ apps_registry_->Update();
+ base::RunLoop().RunUntilIdle();
+
+ std::vector<DriveAppInfo> apps;
+ apps_registry_->GetAppList(&apps);
+ size_t original_count = apps.size();
+
+ // Uninstall an existing app.
+ google_apis::DriveApiErrorCode error = google_apis::DRIVE_OTHER_ERROR;
+ apps_registry_->UninstallApp(
+ "123456788192",
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(error, google_apis::HTTP_NO_CONTENT);
+
+ // Check that the number of apps is decreased by one.
+ apps_registry_->GetAppList(&apps);
+ EXPECT_EQ(original_count - 1, apps.size());
+
+ // Try to uninstall a non-existing app.
+ error = google_apis::DRIVE_OTHER_ERROR;
+ apps_registry_->UninstallApp(
+ "non-existing-app-id",
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(error, google_apis::HTTP_NOT_FOUND);
+
+ // Check that the number is not changed this time.
+ apps_registry_->GetAppList(&apps);
+ EXPECT_EQ(original_count - 1, apps.size());
+}
+
+} // namespace drive
diff --git a/components/drive/drive_notification_manager.cc b/components/drive/drive_notification_manager.cc
new file mode 100644
index 0000000..14e0768
--- /dev/null
+++ b/components/drive/drive_notification_manager.cc
@@ -0,0 +1,160 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/drive_notification_manager.h"
+
+#include "base/metrics/histogram.h"
+#include "components/drive/drive_notification_observer.h"
+#include "components/invalidation/public/invalidation_service.h"
+#include "components/invalidation/public/object_id_invalidation_map.h"
+#include "google/cacheinvalidation/types.pb.h"
+
+namespace drive {
+
+namespace {
+
+// The polling interval time is used when XMPP is disabled.
+const int kFastPollingIntervalInSecs = 60;
+
+// The polling interval time is used when XMPP is enabled. Theoretically
+// polling should be unnecessary if XMPP is enabled, but just in case.
+const int kSlowPollingIntervalInSecs = 300;
+
+// The sync invalidation object ID for Google Drive.
+const char kDriveInvalidationObjectId[] = "CHANGELOG";
+
+} // namespace
+
+DriveNotificationManager::DriveNotificationManager(
+ invalidation::InvalidationService* invalidation_service)
+ : invalidation_service_(invalidation_service),
+ push_notification_registered_(false),
+ push_notification_enabled_(false),
+ observers_notified_(false),
+ polling_timer_(true /* retain_user_task */, false /* is_repeating */),
+ weak_ptr_factory_(this) {
+ DCHECK(invalidation_service_);
+ RegisterDriveNotifications();
+ RestartPollingTimer();
+}
+
+DriveNotificationManager::~DriveNotificationManager() {}
+
+void DriveNotificationManager::Shutdown() {
+ // Unregister for Drive notifications.
+ if (!invalidation_service_ || !push_notification_registered_)
+ return;
+
+ // We unregister the handler without updating unregistering our IDs on
+ // purpose. See the class comment on the InvalidationService interface for
+ // more information.
+ invalidation_service_->UnregisterInvalidationHandler(this);
+ invalidation_service_ = NULL;
+}
+
+void DriveNotificationManager::OnInvalidatorStateChange(
+ syncer::InvalidatorState state) {
+ push_notification_enabled_ = (state == syncer::INVALIDATIONS_ENABLED);
+ if (push_notification_enabled_) {
+ DVLOG(1) << "XMPP Notifications enabled";
+ } else {
+ DVLOG(1) << "XMPP Notifications disabled (state=" << state << ")";
+ }
+ FOR_EACH_OBSERVER(DriveNotificationObserver, observers_,
+ OnPushNotificationEnabled(push_notification_enabled_));
+}
+
+void DriveNotificationManager::OnIncomingInvalidation(
+ const syncer::ObjectIdInvalidationMap& invalidation_map) {
+ DVLOG(2) << "XMPP Drive Notification Received";
+ syncer::ObjectIdSet ids = invalidation_map.GetObjectIds();
+ DCHECK_EQ(1U, ids.size());
+ const invalidation::ObjectId object_id(
+ ipc::invalidation::ObjectSource::COSMO_CHANGELOG,
+ kDriveInvalidationObjectId);
+ DCHECK_EQ(1U, ids.count(object_id));
+
+ // This effectively disables 'local acks'. It tells the invalidations system
+ // to not bother saving invalidations across restarts for us.
+ // See crbug.com/320878.
+ invalidation_map.AcknowledgeAll();
+ NotifyObserversToUpdate(NOTIFICATION_XMPP);
+}
+
+std::string DriveNotificationManager::GetOwnerName() const { return "Drive"; }
+
+void DriveNotificationManager::AddObserver(
+ DriveNotificationObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DriveNotificationManager::RemoveObserver(
+ DriveNotificationObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void DriveNotificationManager::RestartPollingTimer() {
+ const int interval_secs = (push_notification_enabled_ ?
+ kSlowPollingIntervalInSecs :
+ kFastPollingIntervalInSecs);
+ polling_timer_.Stop();
+ polling_timer_.Start(
+ FROM_HERE,
+ base::TimeDelta::FromSeconds(interval_secs),
+ base::Bind(&DriveNotificationManager::NotifyObserversToUpdate,
+ weak_ptr_factory_.GetWeakPtr(),
+ NOTIFICATION_POLLING));
+}
+
+void DriveNotificationManager::NotifyObserversToUpdate(
+ NotificationSource source) {
+ DVLOG(1) << "Notifying observers: " << NotificationSourceToString(source);
+ FOR_EACH_OBSERVER(DriveNotificationObserver, observers_,
+ OnNotificationReceived());
+ if (!observers_notified_) {
+ UMA_HISTOGRAM_BOOLEAN("Drive.PushNotificationInitiallyEnabled",
+ push_notification_enabled_);
+ }
+ observers_notified_ = true;
+
+ // Note that polling_timer_ is not a repeating timer. Restarting manually
+ // here is better as XMPP may be received right before the polling timer is
+ // fired (i.e. we don't notify observers twice in a row).
+ RestartPollingTimer();
+}
+
+void DriveNotificationManager::RegisterDriveNotifications() {
+ DCHECK(!push_notification_enabled_);
+
+ if (!invalidation_service_)
+ return;
+
+ invalidation_service_->RegisterInvalidationHandler(this);
+ syncer::ObjectIdSet ids;
+ ids.insert(invalidation::ObjectId(
+ ipc::invalidation::ObjectSource::COSMO_CHANGELOG,
+ kDriveInvalidationObjectId));
+ CHECK(invalidation_service_->UpdateRegisteredInvalidationIds(this, ids));
+ push_notification_registered_ = true;
+ OnInvalidatorStateChange(invalidation_service_->GetInvalidatorState());
+
+ UMA_HISTOGRAM_BOOLEAN("Drive.PushNotificationRegistered",
+ push_notification_registered_);
+}
+
+// static
+std::string DriveNotificationManager::NotificationSourceToString(
+ NotificationSource source) {
+ switch (source) {
+ case NOTIFICATION_XMPP:
+ return "NOTIFICATION_XMPP";
+ case NOTIFICATION_POLLING:
+ return "NOTIFICATION_POLLING";
+ }
+
+ NOTREACHED();
+ return "";
+}
+
+} // namespace drive
diff --git a/components/drive/drive_notification_manager.h b/components/drive/drive_notification_manager.h
new file mode 100644
index 0000000..5cbb8a7
--- /dev/null
+++ b/components/drive/drive_notification_manager.h
@@ -0,0 +1,98 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_DRIVE_NOTIFICATION_MANAGER_H_
+#define COMPONENTS_DRIVE_DRIVE_NOTIFICATION_MANAGER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/timer/timer.h"
+#include "components/drive/drive_notification_observer.h"
+#include "components/invalidation/public/invalidation_handler.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class ProfileSyncService;
+
+namespace invalidation {
+class InvalidationService;
+}
+
+namespace drive {
+
+// Informs observers when they should check Google Drive for updates.
+// Conditions under which updates should be searched:
+// 1. XMPP invalidation is received from Google Drive.
+// 2. Polling timer counts down.
+class DriveNotificationManager : public KeyedService,
+ public syncer::InvalidationHandler {
+ public:
+ explicit DriveNotificationManager(
+ invalidation::InvalidationService* invalidation_service);
+ ~DriveNotificationManager() override;
+
+ // KeyedService override.
+ void Shutdown() override;
+
+ // syncer::InvalidationHandler implementation.
+ void OnInvalidatorStateChange(syncer::InvalidatorState state) override;
+ void OnIncomingInvalidation(
+ const syncer::ObjectIdInvalidationMap& invalidation_map) override;
+ std::string GetOwnerName() const override;
+
+ void AddObserver(DriveNotificationObserver* observer);
+ void RemoveObserver(DriveNotificationObserver* observer);
+
+ // True when XMPP notification is currently enabled.
+ bool push_notification_enabled() const {
+ return push_notification_enabled_;
+ }
+
+ // True when XMPP notification has been registered.
+ bool push_notification_registered() const {
+ return push_notification_registered_;
+ }
+
+ private:
+ enum NotificationSource {
+ NOTIFICATION_XMPP,
+ NOTIFICATION_POLLING,
+ };
+
+ // Restarts the polling timer. Used for polling-based notification.
+ void RestartPollingTimer();
+
+ // Notifies the observers that it's time to check for updates.
+ // |source| indicates where the notification comes from.
+ void NotifyObserversToUpdate(NotificationSource source);
+
+ // Registers for Google Drive invalidation notifications through XMPP.
+ void RegisterDriveNotifications();
+
+ // Returns a string representation of NotificationSource.
+ static std::string NotificationSourceToString(NotificationSource source);
+
+ invalidation::InvalidationService* invalidation_service_;
+ base::ObserverList<DriveNotificationObserver> observers_;
+
+ // True when Drive File Sync Service is registered for Drive notifications.
+ bool push_notification_registered_;
+ // True if the XMPP-based push notification is currently enabled.
+ bool push_notification_enabled_;
+ // True once observers are notified for the first time.
+ bool observers_notified_;
+
+ // The timer is used for polling based notification. XMPP should usually be
+ // used but notification is done per polling when XMPP is not working.
+ base::Timer polling_timer_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<DriveNotificationManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveNotificationManager);
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DRIVE_NOTIFICATION_MANAGER_H_
diff --git a/components/drive/drive_notification_observer.h b/components/drive/drive_notification_observer.h
new file mode 100644
index 0000000..b04a06b
--- /dev/null
+++ b/components/drive/drive_notification_observer.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_DRIVE_NOTIFICATION_OBSERVER_H_
+#define COMPONENTS_DRIVE_DRIVE_NOTIFICATION_OBSERVER_H_
+
+namespace drive {
+
+// Interface for classes which need to know when to check Google Drive for
+// updates.
+class DriveNotificationObserver {
+ public:
+ // Called when a notification from Google Drive is received.
+ virtual void OnNotificationReceived() = 0;
+
+ // Called when XMPP-based push notification is enabled or disabled.
+ virtual void OnPushNotificationEnabled(bool enabled) {}
+
+ protected:
+ virtual ~DriveNotificationObserver() {}
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DRIVE_NOTIFICATION_OBSERVER_H_
diff --git a/components/drive/drive_uploader.cc b/components/drive/drive_uploader.cc
new file mode 100644
index 0000000..210d1b1
--- /dev/null
+++ b/components/drive/drive_uploader.cc
@@ -0,0 +1,533 @@
+// 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 "components/drive/drive_uploader.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task_runner_util.h"
+#include "components/drive/service/drive_service_interface.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+using google_apis::CancelCallback;
+using google_apis::FileResource;
+using google_apis::DRIVE_CANCELLED;
+using google_apis::DriveApiErrorCode;
+using google_apis::DRIVE_NO_SPACE;
+using google_apis::HTTP_CONFLICT;
+using google_apis::HTTP_CREATED;
+using google_apis::HTTP_FORBIDDEN;
+using google_apis::HTTP_NOT_FOUND;
+using google_apis::HTTP_PRECONDITION;
+using google_apis::HTTP_RESUME_INCOMPLETE;
+using google_apis::HTTP_SUCCESS;
+using google_apis::ProgressCallback;
+using google_apis::UploadRangeResponse;
+
+namespace drive {
+
+namespace {
+// Upload data is split to multiple HTTP request each conveying kUploadChunkSize
+// bytes (except the request for uploading the last chunk of data).
+// The value must be a multiple of 512KB according to the spec of GData WAPI and
+// Drive API v2. It is set to a smaller value than 2^31 for working around
+// server side error (crbug.com/264089).
+const int64 kUploadChunkSize = (1LL << 30); // 1GB
+// Maximum file size to be uploaded by multipart requests. The file that is
+// larger than the size is processed by resumable upload.
+const int64 kMaxMultipartUploadSize = (1LL << 20); // 1MB
+
+// Drive upload protocol. This is used to back a histogram. Sync this with UMA
+// enum "DriveUploadProtocol" and treat this as append-only.
+enum DriveUploadProtocol {
+ UPLOAD_METHOD_RESUMABLE,
+ UPLOAD_METHOD_MULTIPART,
+ UPLOAD_METHOD_BATCH,
+ UPLOAD_METHOD_MAX_VALUE
+};
+
+void RecordDriveUploadProtocol(DriveUploadProtocol protocol) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Drive.UploadProtocol", protocol, UPLOAD_METHOD_MAX_VALUE);
+}
+} // namespace
+
+// Refcounted helper class to manage batch request. DriveUploader uses the class
+// for keeping the BatchRequestConfigurator instance while it prepares upload
+// file information asynchronously. DriveUploader discard the reference after
+// getting file information and the instance will be destroyed after all
+// preparations complete. At that time, the helper instance commits owned batch
+// request at the destrutor.
+class DriveUploader::RefCountedBatchRequest
+ : public base::RefCounted<RefCountedBatchRequest> {
+ public:
+ RefCountedBatchRequest(
+ scoped_ptr<BatchRequestConfiguratorInterface> configurator)
+ : configurator_(configurator.Pass()) {}
+
+ // Gets pointer of BatchRequestConfiguratorInterface owned by the instance.
+ BatchRequestConfiguratorInterface* configurator() const {
+ return configurator_.get();
+ }
+
+ private:
+ friend class base::RefCounted<RefCountedBatchRequest>;
+ ~RefCountedBatchRequest() { configurator_->Commit(); }
+ scoped_ptr<BatchRequestConfiguratorInterface> configurator_;
+};
+
+// Structure containing current upload information of file, passed between
+// DriveServiceInterface methods and callbacks.
+struct DriveUploader::UploadFileInfo {
+ UploadFileInfo(const base::FilePath& local_path,
+ const std::string& content_type,
+ const UploadCompletionCallback& callback,
+ const ProgressCallback& progress_callback)
+ : file_path(local_path),
+ content_type(content_type),
+ completion_callback(callback),
+ progress_callback(progress_callback),
+ content_length(0),
+ next_start_position(-1),
+ power_save_blocker(content::PowerSaveBlocker::Create(
+ content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+ content::PowerSaveBlocker::kReasonOther,
+ "Upload in progress")),
+ cancelled(false),
+ weak_ptr_factory_(this) {}
+
+ ~UploadFileInfo() {
+ }
+
+ // Useful for printf debugging.
+ std::string DebugString() const {
+ return "file_path=[" + file_path.AsUTF8Unsafe() +
+ "], content_type=[" + content_type +
+ "], content_length=[" + base::UintToString(content_length) +
+ "]";
+ }
+
+ // Returns the callback to cancel the upload represented by this struct.
+ CancelCallback GetCancelCallback() {
+ return base::Bind(&UploadFileInfo::Cancel, weak_ptr_factory_.GetWeakPtr());
+ }
+
+ // The local file path of the file to be uploaded.
+ const base::FilePath file_path;
+
+ // Content-Type of file.
+ const std::string content_type;
+
+ // Callback to be invoked once the upload has finished.
+ const UploadCompletionCallback completion_callback;
+
+ // Callback to periodically notify the upload progress.
+ const ProgressCallback progress_callback;
+
+ // Location URL where file is to be uploaded to, returned from
+ // InitiateUpload. Used for the subsequent ResumeUpload requests.
+ GURL upload_location;
+
+ // Header content-Length.
+ int64 content_length;
+
+ int64 next_start_position;
+
+ // Blocks system suspend while upload is in progress.
+ scoped_ptr<content::PowerSaveBlocker> power_save_blocker;
+
+ // Fields for implementing cancellation. |cancel_callback| is non-null if
+ // there is an in-flight HTTP request. In that case, |cancell_callback| will
+ // cancel the operation. |cancelled| is initially false and turns to true
+ // once Cancel() is called. DriveUploader will check this field before after
+ // an async task other than HTTP requests and cancels the subsequent requests
+ // if this is flagged to true.
+ CancelCallback cancel_callback;
+ bool cancelled;
+
+ private:
+ // Cancels the upload represented by this struct.
+ void Cancel() {
+ cancelled = true;
+ if (!cancel_callback.is_null())
+ cancel_callback.Run();
+ }
+
+ base::WeakPtrFactory<UploadFileInfo> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(UploadFileInfo);
+};
+
+DriveUploader::DriveUploader(
+ DriveServiceInterface* drive_service,
+ const scoped_refptr<base::TaskRunner>& blocking_task_runner)
+ : drive_service_(drive_service),
+ blocking_task_runner_(blocking_task_runner),
+ weak_ptr_factory_(this) {
+}
+
+DriveUploader::~DriveUploader() {}
+
+CancelCallback DriveUploader::UploadNewFile(
+ const std::string& parent_resource_id,
+ const base::FilePath& local_file_path,
+ const std::string& title,
+ const std::string& content_type,
+ const UploadNewFileOptions& options,
+ const UploadCompletionCallback& callback,
+ const ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!parent_resource_id.empty());
+ DCHECK(!local_file_path.empty());
+ DCHECK(!title.empty());
+ DCHECK(!content_type.empty());
+ DCHECK(!callback.is_null());
+
+ return StartUploadFile(
+ scoped_ptr<UploadFileInfo>(new UploadFileInfo(
+ local_file_path, content_type, callback, progress_callback)),
+ base::Bind(&DriveUploader::CallUploadServiceAPINewFile,
+ weak_ptr_factory_.GetWeakPtr(), parent_resource_id, title,
+ options, current_batch_request_));
+}
+
+void DriveUploader::StartBatchProcessing() {
+ DCHECK(current_batch_request_ == nullptr);
+ current_batch_request_ =
+ new RefCountedBatchRequest(drive_service_->StartBatchRequest().Pass());
+}
+
+void DriveUploader::StopBatchProcessing() {
+ current_batch_request_ = nullptr;
+}
+
+CancelCallback DriveUploader::UploadExistingFile(
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const std::string& content_type,
+ const UploadExistingFileOptions& options,
+ const UploadCompletionCallback& callback,
+ const ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!resource_id.empty());
+ DCHECK(!local_file_path.empty());
+ DCHECK(!content_type.empty());
+ DCHECK(!callback.is_null());
+
+ return StartUploadFile(
+ scoped_ptr<UploadFileInfo>(new UploadFileInfo(
+ local_file_path, content_type, callback, progress_callback)),
+ base::Bind(&DriveUploader::CallUploadServiceAPIExistingFile,
+ weak_ptr_factory_.GetWeakPtr(), resource_id, options,
+ current_batch_request_));
+}
+
+CancelCallback DriveUploader::ResumeUploadFile(
+ const GURL& upload_location,
+ const base::FilePath& local_file_path,
+ const std::string& content_type,
+ const UploadCompletionCallback& callback,
+ const ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!local_file_path.empty());
+ DCHECK(!content_type.empty());
+ DCHECK(!callback.is_null());
+
+ scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo(
+ local_file_path, content_type, callback, progress_callback));
+ upload_file_info->upload_location = upload_location;
+
+ return StartUploadFile(
+ upload_file_info.Pass(),
+ base::Bind(&DriveUploader::StartGetUploadStatus,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+CancelCallback DriveUploader::StartUploadFile(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ const StartInitiateUploadCallback& start_initiate_upload_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "Uploading file: " << upload_file_info->DebugString();
+
+ UploadFileInfo* info_ptr = upload_file_info.get();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&base::GetFileSize,
+ info_ptr->file_path,
+ &info_ptr->content_length),
+ base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&upload_file_info),
+ start_initiate_upload_callback));
+ return info_ptr->GetCancelCallback();
+}
+
+void DriveUploader::StartUploadFileAfterGetFileSize(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ const StartInitiateUploadCallback& start_initiate_upload_callback,
+ bool get_file_size_result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!get_file_size_result) {
+ UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND);
+ return;
+ }
+ DCHECK_GE(upload_file_info->content_length, 0);
+
+ if (upload_file_info->cancelled) {
+ UploadFailed(upload_file_info.Pass(), DRIVE_CANCELLED);
+ return;
+ }
+ start_initiate_upload_callback.Run(upload_file_info.Pass());
+}
+
+void DriveUploader::CallUploadServiceAPINewFile(
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const scoped_refptr<RefCountedBatchRequest>& batch_request,
+ scoped_ptr<UploadFileInfo> upload_file_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ UploadFileInfo* const info_ptr = upload_file_info.get();
+ if (info_ptr->content_length <= kMaxMultipartUploadSize) {
+ DriveServiceBatchOperationsInterface* service;
+ // If this is a batched request, calls the API on the request instead.
+ if (batch_request.get()) {
+ service = batch_request->configurator();
+ RecordDriveUploadProtocol(UPLOAD_METHOD_BATCH);
+ } else {
+ service = drive_service_;
+ RecordDriveUploadProtocol(UPLOAD_METHOD_MULTIPART);
+ }
+ info_ptr->cancel_callback = service->MultipartUploadNewFile(
+ info_ptr->content_type, info_ptr->content_length, parent_resource_id,
+ title, info_ptr->file_path, options,
+ base::Bind(&DriveUploader::OnMultipartUploadComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&upload_file_info)),
+ info_ptr->progress_callback);
+ } else {
+ RecordDriveUploadProtocol(UPLOAD_METHOD_RESUMABLE);
+ info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile(
+ info_ptr->content_type, info_ptr->content_length, parent_resource_id,
+ title, options, base::Bind(&DriveUploader::OnUploadLocationReceived,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&upload_file_info)));
+ }
+}
+
+void DriveUploader::CallUploadServiceAPIExistingFile(
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const scoped_refptr<RefCountedBatchRequest>& batch_request,
+ scoped_ptr<UploadFileInfo> upload_file_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ UploadFileInfo* const info_ptr = upload_file_info.get();
+ if (info_ptr->content_length <= kMaxMultipartUploadSize) {
+ DriveServiceBatchOperationsInterface* service;
+ // If this is a batched request, calls the API on the request instead.
+ if (batch_request.get()) {
+ service = batch_request->configurator();
+ RecordDriveUploadProtocol(UPLOAD_METHOD_BATCH);
+ } else {
+ service = drive_service_;
+ RecordDriveUploadProtocol(UPLOAD_METHOD_MULTIPART);
+ }
+ info_ptr->cancel_callback = service->MultipartUploadExistingFile(
+ info_ptr->content_type, info_ptr->content_length, resource_id,
+ info_ptr->file_path, options,
+ base::Bind(&DriveUploader::OnMultipartUploadComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&upload_file_info)),
+ info_ptr->progress_callback);
+ } else {
+ RecordDriveUploadProtocol(UPLOAD_METHOD_RESUMABLE);
+ info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile(
+ info_ptr->content_type, info_ptr->content_length, resource_id, options,
+ base::Bind(&DriveUploader::OnUploadLocationReceived,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&upload_file_info)));
+ }
+}
+
+void DriveUploader::OnUploadLocationReceived(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ DriveApiErrorCode code,
+ const GURL& upload_location) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DVLOG(1) << "Got upload location [" << upload_location.spec()
+ << "] for [" << upload_file_info->file_path.value() << "]";
+
+ if (code != HTTP_SUCCESS) {
+ if (code == HTTP_PRECONDITION)
+ code = HTTP_CONFLICT; // ETag mismatch.
+ UploadFailed(upload_file_info.Pass(), code);
+ return;
+ }
+
+ upload_file_info->upload_location = upload_location;
+ upload_file_info->next_start_position = 0;
+ UploadNextChunk(upload_file_info.Pass());
+}
+
+void DriveUploader::StartGetUploadStatus(
+ scoped_ptr<UploadFileInfo> upload_file_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(upload_file_info);
+
+ UploadFileInfo* info_ptr = upload_file_info.get();
+ info_ptr->cancel_callback = drive_service_->GetUploadStatus(
+ info_ptr->upload_location,
+ info_ptr->content_length,
+ base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&upload_file_info)));
+}
+
+void DriveUploader::UploadNextChunk(
+ scoped_ptr<UploadFileInfo> upload_file_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(upload_file_info);
+ DCHECK_GE(upload_file_info->next_start_position, 0);
+ DCHECK_LE(upload_file_info->next_start_position,
+ upload_file_info->content_length);
+
+ if (upload_file_info->cancelled) {
+ UploadFailed(upload_file_info.Pass(), DRIVE_CANCELLED);
+ return;
+ }
+
+ // Limit the size of data uploaded per each request by kUploadChunkSize.
+ const int64 end_position = std::min(
+ upload_file_info->content_length,
+ upload_file_info->next_start_position + kUploadChunkSize);
+
+ UploadFileInfo* info_ptr = upload_file_info.get();
+ info_ptr->cancel_callback = drive_service_->ResumeUpload(
+ info_ptr->upload_location,
+ info_ptr->next_start_position,
+ end_position,
+ info_ptr->content_length,
+ info_ptr->content_type,
+ info_ptr->file_path,
+ base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&upload_file_info)),
+ base::Bind(&DriveUploader::OnUploadProgress,
+ weak_ptr_factory_.GetWeakPtr(),
+ info_ptr->progress_callback,
+ info_ptr->next_start_position,
+ info_ptr->content_length));
+}
+
+void DriveUploader::OnUploadRangeResponseReceived(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ const UploadRangeResponse& response,
+ scoped_ptr<FileResource> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (response.code == HTTP_CREATED || response.code == HTTP_SUCCESS) {
+ // When uploading a new file, we expect HTTP_CREATED, and when uploading
+ // an existing file (to overwrite), we expect HTTP_SUCCESS.
+ // There is an exception: if we uploading an empty file, uploading a new
+ // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the
+ // fix should be uploading the metadata only. However, to keep the
+ // compatibility with GData WAPI during the migration period, we just
+ // relax the condition here.
+ // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
+ // code is gone.
+ DVLOG(1) << "Successfully created uploaded file=["
+ << upload_file_info->file_path.value() << "]";
+
+ // Done uploading.
+ upload_file_info->completion_callback.Run(
+ HTTP_SUCCESS, GURL(), entry.Pass());
+ return;
+ }
+
+ // ETag mismatch.
+ if (response.code == HTTP_PRECONDITION) {
+ UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT);
+ return;
+ }
+
+ // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0
+ // (meaning that the data is uploaded from the beginning of the file),
+ // proceed to upload the next chunk.
+ if (response.code != HTTP_RESUME_INCOMPLETE ||
+ response.start_position_received != 0) {
+ DVLOG(1)
+ << "UploadNextChunk http code=" << response.code
+ << ", start_position_received=" << response.start_position_received
+ << ", end_position_received=" << response.end_position_received;
+ UploadFailed(
+ upload_file_info.Pass(),
+ response.code == HTTP_FORBIDDEN ? DRIVE_NO_SPACE : response.code);
+ return;
+ }
+
+ DVLOG(1) << "Received range " << response.start_position_received
+ << "-" << response.end_position_received
+ << " for [" << upload_file_info->file_path.value() << "]";
+
+ upload_file_info->next_start_position = response.end_position_received;
+ UploadNextChunk(upload_file_info.Pass());
+}
+
+void DriveUploader::OnUploadProgress(const ProgressCallback& callback,
+ int64 start_position,
+ int64 total_size,
+ int64 progress_of_chunk,
+ int64 total_of_chunk) {
+ if (!callback.is_null())
+ callback.Run(start_position + progress_of_chunk, total_size);
+}
+
+void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
+ DriveApiErrorCode error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
+
+ if (upload_file_info->next_start_position < 0) {
+ // Discard the upload location because no request could succeed with it.
+ // Maybe it's obsolete.
+ upload_file_info->upload_location = GURL();
+ }
+
+ upload_file_info->completion_callback.Run(
+ error, upload_file_info->upload_location, scoped_ptr<FileResource>());
+}
+
+void DriveUploader::OnMultipartUploadComplete(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ google_apis::DriveApiErrorCode error,
+ scoped_ptr<FileResource> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error == HTTP_CREATED || error == HTTP_SUCCESS) {
+ DVLOG(1) << "Successfully created uploaded file=["
+ << upload_file_info->file_path.value() << "]";
+ // Done uploading.
+ upload_file_info->completion_callback.Run(
+ HTTP_SUCCESS, upload_file_info->upload_location, entry.Pass());
+ } else {
+ DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
+ if (error == HTTP_PRECONDITION)
+ error = HTTP_CONFLICT; // ETag mismatch.
+ upload_file_info->completion_callback.Run(
+ error, upload_file_info->upload_location, scoped_ptr<FileResource>());
+ }
+}
+
+} // namespace drive
diff --git a/components/drive/drive_uploader.h b/components/drive/drive_uploader.h
new file mode 100644
index 0000000..e383614
--- /dev/null
+++ b/components/drive/drive_uploader.h
@@ -0,0 +1,240 @@
+// 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.
+
+#ifndef COMPONENTS_DRIVE_DRIVE_UPLOADER_H_
+#define COMPONENTS_DRIVE_DRIVE_UPLOADER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/service/drive_service_interface.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class TaskRunner;
+}
+
+namespace google_apis {
+struct UploadRangeResponse;
+}
+
+namespace drive {
+class DriveServiceInterface;
+
+// Callback to be invoked once the upload has completed.
+// |upload_location| will be returned when the uploading process is started but
+// terminated before the completion due to some errors. It can be used to
+// resume it.
+typedef base::Callback<void(
+ google_apis::DriveApiErrorCode error,
+ const GURL& upload_location,
+ scoped_ptr<google_apis::FileResource> resource_entry)>
+ UploadCompletionCallback;
+
+class DriveUploaderInterface {
+ public:
+ virtual ~DriveUploaderInterface() {}
+
+ // Starts batch processing for upload requests. All requests which upload
+ // small files (less than kMaxMultipartUploadSize) between
+ // |StartBatchProcessing| and |StopBatchProcessing| are sent as a single batch
+ // request.
+ virtual void StartBatchProcessing() = 0;
+
+ // Stops batch processing. Must be called after calling |StartBatchProcessing|
+ // to commit requests.
+ virtual void StopBatchProcessing() = 0;
+
+ // Uploads a new file to a directory specified by |upload_location|.
+ // Returns a callback for cancelling the uploading job.
+ //
+ // parent_resource_id:
+ // resource id of the destination directory.
+ //
+ // local_file_path:
+ // The path to the local file to be uploaded.
+ //
+ // title:
+ // The title (file name) of the file to be uploaded.
+ //
+ // content_type:
+ // The content type of the file to be uploaded.
+ //
+ // callback:
+ // Called when an upload is done regardless of it was successful or not.
+ // Must not be null.
+ //
+ // progress_callback:
+ // Periodically called back with the total number of bytes sent so far.
+ // May be null if the information is not needed.
+ virtual google_apis::CancelCallback UploadNewFile(
+ const std::string& parent_resource_id,
+ const base::FilePath& local_file_path,
+ const std::string& title,
+ const std::string& content_type,
+ const UploadNewFileOptions& options,
+ const UploadCompletionCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) = 0;
+
+ // Uploads an existing file (a file that already exists on Drive).
+ //
+ // See comments at UploadNewFile about common parameters and the return value.
+ //
+ // resource_id:
+ // resource id of the existing file to be overwritten.
+ //
+ // etag:
+ // Expected ETag for the destination file. If it does not match, the upload
+ // fails with UPLOAD_ERROR_CONFLICT.
+ // If |etag| is empty, the test is skipped.
+ virtual google_apis::CancelCallback UploadExistingFile(
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const std::string& content_type,
+ const UploadExistingFileOptions& options,
+ const UploadCompletionCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) = 0;
+
+ // Resumes the uploading process terminated before the completion.
+ // |upload_location| should be the one returned via UploadCompletionCallback
+ // for previous invocation. |drive_file_path|, |local_file_path| and
+ // |content_type| must be set to the same ones for previous invocation.
+ //
+ // See comments at UploadNewFile about common parameters and the return value.
+ virtual google_apis::CancelCallback ResumeUploadFile(
+ const GURL& upload_location,
+ const base::FilePath& local_file_path,
+ const std::string& content_type,
+ const UploadCompletionCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) = 0;
+};
+
+class DriveUploader : public DriveUploaderInterface {
+ public:
+ DriveUploader(DriveServiceInterface* drive_service,
+ const scoped_refptr<base::TaskRunner>& blocking_task_runner);
+ ~DriveUploader() override;
+
+ // DriveUploaderInterface overrides.
+ void StartBatchProcessing() override;
+ void StopBatchProcessing() override;
+ google_apis::CancelCallback UploadNewFile(
+ const std::string& parent_resource_id,
+ const base::FilePath& local_file_path,
+ const std::string& title,
+ const std::string& content_type,
+ const UploadNewFileOptions& options,
+ const UploadCompletionCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback UploadExistingFile(
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const std::string& content_type,
+ const UploadExistingFileOptions& options,
+ const UploadCompletionCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback ResumeUploadFile(
+ const GURL& upload_location,
+ const base::FilePath& local_file_path,
+ const std::string& content_type,
+ const UploadCompletionCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+
+ private:
+ class RefCountedBatchRequest;
+ struct UploadFileInfo;
+ typedef base::Callback<void(scoped_ptr<UploadFileInfo> upload_file_info)>
+ StartInitiateUploadCallback;
+
+ // Starts uploading a file with |upload_file_info|.
+ google_apis::CancelCallback StartUploadFile(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ const StartInitiateUploadCallback& start_initiate_upload_callback);
+ void StartUploadFileAfterGetFileSize(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ const StartInitiateUploadCallback& start_initiate_upload_callback,
+ bool get_file_size_result);
+
+ // Checks file size and call InitiateUploadNewFile or MultipartUploadNewFile
+ // API. Upon completion, OnUploadLocationReceived (for InitiateUploadNewFile)
+ // or OnMultipartUploadComplete (for MultipartUploadNewFile) should be called.
+ // If |batch_request| is non-null, it calls the API function on the batch
+ // request.
+ void CallUploadServiceAPINewFile(
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const scoped_refptr<RefCountedBatchRequest>& batch_request,
+ scoped_ptr<UploadFileInfo> upload_file_info);
+
+ // Checks file size and call InitiateUploadExistingFile or
+ // MultipartUploadExistingFile API. Upon completion, OnUploadLocationReceived
+ // (for InitiateUploadExistingFile) or OnMultipartUploadComplete (for
+ // MultipartUploadExistingFile) should be called.
+ // If |batch_request| is non-null, it calls the API function on the batch
+ // request.
+ void CallUploadServiceAPIExistingFile(
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const scoped_refptr<RefCountedBatchRequest>& batch_request,
+ scoped_ptr<UploadFileInfo> upload_file_info);
+
+ // DriveService callback for InitiateUpload.
+ void OnUploadLocationReceived(scoped_ptr<UploadFileInfo> upload_file_info,
+ google_apis::DriveApiErrorCode code,
+ const GURL& upload_location);
+
+ // Starts to get the current upload status for the file uploading.
+ // Upon completion, OnUploadRangeResponseReceived should be called.
+ void StartGetUploadStatus(scoped_ptr<UploadFileInfo> upload_file_info);
+
+ // Uploads the next chunk of data from the file.
+ void UploadNextChunk(scoped_ptr<UploadFileInfo> upload_file_info);
+
+ // DriveService callback for ResumeUpload.
+ void OnUploadRangeResponseReceived(
+ scoped_ptr<UploadFileInfo> upload_file_info,
+ const google_apis::UploadRangeResponse& response,
+ scoped_ptr<google_apis::FileResource> entry);
+ void OnUploadProgress(const google_apis::ProgressCallback& callback,
+ int64 start_position,
+ int64 total_size,
+ int64 progress_of_chunk,
+ int64 total_of_chunk);
+
+ // Handles failed uploads.
+ void UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
+ google_apis::DriveApiErrorCode error);
+
+ // Handles completion/error of multipart uploading.
+ void OnMultipartUploadComplete(scoped_ptr<UploadFileInfo> upload_file_info,
+ google_apis::DriveApiErrorCode error,
+ scoped_ptr<google_apis::FileResource> entry);
+
+ // The class is expected to run on UI thread.
+ base::ThreadChecker thread_checker_;
+
+ // The lifetime of this object should be guaranteed to exceed that of the
+ // DriveUploader instance.
+ DriveServiceInterface* drive_service_; // Not owned by this class.
+
+ scoped_refptr<base::TaskRunner> blocking_task_runner_;
+ scoped_refptr<RefCountedBatchRequest> current_batch_request_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<DriveUploader> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(DriveUploader);
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DRIVE_UPLOADER_H_
diff --git a/components/drive/drive_uploader_unittest.cc b/components/drive/drive_uploader_unittest.cc
new file mode 100644
index 0000000..29fbf26ac
--- /dev/null
+++ b/components/drive/drive_uploader_unittest.cc
@@ -0,0 +1,955 @@
+// 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 "components/drive/drive_uploader.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/drive/service/dummy_drive_service.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using google_apis::CancelCallback;
+using google_apis::FileResource;
+using google_apis::DriveApiErrorCode;
+using google_apis::DRIVE_NO_CONNECTION;
+using google_apis::DRIVE_OTHER_ERROR;
+using google_apis::HTTP_CONFLICT;
+using google_apis::HTTP_CREATED;
+using google_apis::HTTP_NOT_FOUND;
+using google_apis::HTTP_PRECONDITION;
+using google_apis::HTTP_RESUME_INCOMPLETE;
+using google_apis::HTTP_SUCCESS;
+using google_apis::InitiateUploadCallback;
+using google_apis::ProgressCallback;
+using google_apis::UploadRangeResponse;
+using google_apis::drive::UploadRangeCallback;
+namespace test_util = google_apis::test_util;
+
+namespace drive {
+
+namespace {
+
+const char kTestDummyMd5[] = "dummy_md5";
+const char kTestDocumentTitle[] = "Hello world";
+const char kTestInitiateUploadParentResourceId[] = "parent_resource_id";
+const char kTestInitiateUploadResourceId[] = "resource_id";
+const char kTestMimeType[] = "text/plain";
+const char kTestUploadNewFileURL[] = "http://test/upload_location/new_file";
+const char kTestUploadExistingFileURL[] =
+ "http://test/upload_location/existing_file";
+const int64 kUploadChunkSize = 1024 * 1024 * 1024;
+const char kTestETag[] = "test_etag";
+
+CancelCallback SendMultipartUploadResult(
+ DriveApiErrorCode response_code,
+ int64 content_length,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) {
+ // Callback progress
+ if (!progress_callback.is_null()) {
+ // For the testing purpose, it always notifies the progress at the end of
+ // whole file uploading.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(progress_callback, content_length, content_length));
+ }
+
+ // MultipartUploadXXXFile is an asynchronous function, so don't callback
+ // directly.
+ scoped_ptr<FileResource> entry;
+ entry.reset(new FileResource);
+ entry->set_md5_checksum(kTestDummyMd5);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, response_code, base::Passed(&entry)));
+ return CancelCallback();
+}
+
+// Mock DriveService that verifies if the uploaded content matches the preset
+// expectation.
+class MockDriveServiceWithUploadExpectation : public DummyDriveService {
+ public:
+ // Sets up an expected upload content. InitiateUpload and ResumeUpload will
+ // verify that the specified data is correctly uploaded.
+ MockDriveServiceWithUploadExpectation(
+ const base::FilePath& expected_upload_file,
+ int64 expected_content_length)
+ : expected_upload_file_(expected_upload_file),
+ expected_content_length_(expected_content_length),
+ received_bytes_(0),
+ resume_upload_call_count_(0),
+ multipart_upload_call_count_(0) {}
+
+ int64 received_bytes() const { return received_bytes_; }
+ void set_received_bytes(int64 received_bytes) {
+ received_bytes_ = received_bytes;
+ }
+
+ int64 resume_upload_call_count() const { return resume_upload_call_count_; }
+ int64 multipart_upload_call_count() const {
+ return multipart_upload_call_count_;
+ }
+
+ private:
+ // DriveServiceInterface overrides.
+ // Handles a request for obtaining an upload location URL.
+ CancelCallback InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const InitiateUploadCallback& callback) override {
+ EXPECT_EQ(kTestDocumentTitle, title);
+ EXPECT_EQ(kTestMimeType, content_type);
+ EXPECT_EQ(expected_content_length_, content_length);
+ EXPECT_EQ(kTestInitiateUploadParentResourceId, parent_resource_id);
+
+ // Calls back the upload URL for subsequent ResumeUpload requests.
+ // InitiateUpload is an asynchronous function, so don't callback directly.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL)));
+ return CancelCallback();
+ }
+
+ CancelCallback InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const InitiateUploadCallback& callback) override {
+ EXPECT_EQ(kTestMimeType, content_type);
+ EXPECT_EQ(expected_content_length_, content_length);
+ EXPECT_EQ(kTestInitiateUploadResourceId, resource_id);
+
+ if (!options.etag.empty() && options.etag != kTestETag) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, HTTP_PRECONDITION, GURL()));
+ return CancelCallback();
+ }
+
+ // Calls back the upload URL for subsequent ResumeUpload requests.
+ // InitiateUpload is an asynchronous function, so don't callback directly.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadExistingFileURL)));
+ return CancelCallback();
+ }
+
+ // Handles a request for uploading a chunk of bytes.
+ CancelCallback ResumeUpload(
+ const GURL& upload_location,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const UploadRangeCallback& callback,
+ const ProgressCallback& progress_callback) override {
+ // The upload range should start from the current first unreceived byte.
+ EXPECT_EQ(received_bytes_, start_position);
+ EXPECT_EQ(expected_upload_file_, local_file_path);
+
+ // The upload data must be split into 512KB chunks.
+ const int64 expected_chunk_end =
+ std::min(received_bytes_ + kUploadChunkSize, expected_content_length_);
+ EXPECT_EQ(expected_chunk_end, end_position);
+
+ // The upload URL returned by InitiateUpload() must be used.
+ EXPECT_TRUE(GURL(kTestUploadNewFileURL) == upload_location ||
+ GURL(kTestUploadExistingFileURL) == upload_location);
+
+ // Other parameters should be the exact values passed to DriveUploader.
+ EXPECT_EQ(expected_content_length_, content_length);
+ EXPECT_EQ(kTestMimeType, content_type);
+
+ // Update the internal status of the current upload session.
+ resume_upload_call_count_++;
+ received_bytes_ = end_position;
+
+ // Callback progress
+ if (!progress_callback.is_null()) {
+ // For the testing purpose, it always notifies the progress at the end of
+ // each chunk uploading.
+ int64 chunk_size = end_position - start_position;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(progress_callback, chunk_size, chunk_size));
+ }
+
+ SendUploadRangeResponse(upload_location, callback);
+ return CancelCallback();
+ }
+
+ // Handles a request to fetch the current upload status.
+ CancelCallback GetUploadStatus(const GURL& upload_location,
+ int64 content_length,
+ const UploadRangeCallback& callback) override {
+ EXPECT_EQ(expected_content_length_, content_length);
+ // The upload URL returned by InitiateUpload() must be used.
+ EXPECT_TRUE(GURL(kTestUploadNewFileURL) == upload_location ||
+ GURL(kTestUploadExistingFileURL) == upload_location);
+
+ SendUploadRangeResponse(upload_location, callback);
+ return CancelCallback();
+ }
+
+ // Runs |callback| with the current upload status.
+ void SendUploadRangeResponse(const GURL& upload_location,
+ const UploadRangeCallback& callback) {
+ // Callback with response.
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> entry;
+ if (received_bytes_ == expected_content_length_) {
+ DriveApiErrorCode response_code =
+ upload_location == GURL(kTestUploadNewFileURL) ?
+ HTTP_CREATED : HTTP_SUCCESS;
+ response = UploadRangeResponse(response_code, -1, -1);
+
+ entry.reset(new FileResource);
+ entry->set_md5_checksum(kTestDummyMd5);
+ } else {
+ response = UploadRangeResponse(
+ HTTP_RESUME_INCOMPLETE, 0, received_bytes_);
+ }
+ // ResumeUpload is an asynchronous function, so don't callback directly.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, response, base::Passed(&entry)));
+ }
+
+ CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override {
+ EXPECT_EQ(kTestMimeType, content_type);
+ EXPECT_EQ(expected_content_length_, content_length);
+ EXPECT_EQ(kTestInitiateUploadParentResourceId, parent_resource_id);
+ EXPECT_EQ(kTestDocumentTitle, title);
+ EXPECT_EQ(expected_upload_file_, local_file_path);
+
+ received_bytes_ = content_length;
+ multipart_upload_call_count_++;
+ return SendMultipartUploadResult(HTTP_CREATED, content_length, callback,
+ progress_callback);
+ }
+
+ CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override {
+ EXPECT_EQ(kTestMimeType, content_type);
+ EXPECT_EQ(expected_content_length_, content_length);
+ EXPECT_EQ(kTestInitiateUploadResourceId, resource_id);
+ EXPECT_EQ(expected_upload_file_, local_file_path);
+
+ if (!options.etag.empty() && options.etag != kTestETag) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_PRECONDITION,
+ base::Passed(make_scoped_ptr<FileResource>(NULL))));
+ return CancelCallback();
+ }
+
+ received_bytes_ = content_length;
+ multipart_upload_call_count_++;
+ return SendMultipartUploadResult(HTTP_SUCCESS, content_length, callback,
+ progress_callback);
+ }
+
+ const base::FilePath expected_upload_file_;
+ const int64 expected_content_length_;
+ int64 received_bytes_;
+ int64 resume_upload_call_count_;
+ int64 multipart_upload_call_count_;
+};
+
+// Mock DriveService that returns a failure at InitiateUpload().
+class MockDriveServiceNoConnectionAtInitiate : public DummyDriveService {
+ // Returns error.
+ CancelCallback InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const InitiateUploadCallback& callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, DRIVE_NO_CONNECTION, GURL()));
+ return CancelCallback();
+ }
+
+ CancelCallback InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const InitiateUploadCallback& callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, DRIVE_NO_CONNECTION, GURL()));
+ return CancelCallback();
+ }
+
+ // Should not be used.
+ CancelCallback ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const UploadRangeCallback& callback,
+ const ProgressCallback& progress_callback) override {
+ NOTREACHED();
+ return CancelCallback();
+ }
+
+ CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, DRIVE_NO_CONNECTION,
+ base::Passed(make_scoped_ptr<FileResource>(NULL))));
+ return CancelCallback();
+ }
+
+ CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, DRIVE_NO_CONNECTION,
+ base::Passed(make_scoped_ptr<FileResource>(NULL))));
+ return CancelCallback();
+ }
+};
+
+// Mock DriveService that returns a failure at ResumeUpload().
+class MockDriveServiceNoConnectionAtResume : public DummyDriveService {
+ // Succeeds and returns an upload location URL.
+ CancelCallback InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const InitiateUploadCallback& callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL)));
+ return CancelCallback();
+ }
+
+ CancelCallback InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const InitiateUploadCallback& callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadExistingFileURL)));
+ return CancelCallback();
+ }
+
+ // Returns error.
+ CancelCallback ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const UploadRangeCallback& callback,
+ const ProgressCallback& progress_callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback,
+ UploadRangeResponse(DRIVE_NO_CONNECTION, -1, -1),
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+};
+
+// Mock DriveService that returns a failure at GetUploadStatus().
+class MockDriveServiceNoConnectionAtGetUploadStatus : public DummyDriveService {
+ // Returns error.
+ CancelCallback GetUploadStatus(const GURL& upload_url,
+ int64 content_length,
+ const UploadRangeCallback& callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+ base::Bind(callback,
+ UploadRangeResponse(DRIVE_NO_CONNECTION, -1, -1),
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+};
+
+class DriveUploaderTest : public testing::Test {
+ public:
+ void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+ protected:
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir temp_dir_;
+};
+
+} // namespace
+
+TEST_F(DriveUploaderTest, UploadExisting0KB) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 0, &local_path, &data));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ std::vector<test_util::ProgressInfo> upload_progress_values;
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId, local_path, kTestMimeType,
+ UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(0, mock_service.resume_upload_call_count());
+ EXPECT_EQ(1, mock_service.multipart_upload_call_count());
+ EXPECT_EQ(0, mock_service.received_bytes());
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_TRUE(upload_location.is_empty());
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
+ ASSERT_EQ(1U, upload_progress_values.size());
+ EXPECT_EQ(test_util::ProgressInfo(0, 0), upload_progress_values[0]);
+}
+
+TEST_F(DriveUploaderTest, UploadExisting512KB) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 512 * 1024, &local_path, &data));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ std::vector<test_util::ProgressInfo> upload_progress_values;
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId, local_path, kTestMimeType,
+ UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ // 512KB upload should be uploaded as multipart body.
+ EXPECT_EQ(0, mock_service.resume_upload_call_count());
+ EXPECT_EQ(1, mock_service.multipart_upload_call_count());
+ EXPECT_EQ(512 * 1024, mock_service.received_bytes());
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_TRUE(upload_location.is_empty());
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
+ ASSERT_EQ(1U, upload_progress_values.size());
+ EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 512 * 1024),
+ upload_progress_values[0]);
+}
+
+TEST_F(DriveUploaderTest, UploadExisting2MB) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 2 * 1024 * 1024, &local_path, &data));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ std::vector<test_util::ProgressInfo> upload_progress_values;
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId, local_path, kTestMimeType,
+ UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ // 2MB upload should not be split into multiple chunks.
+ EXPECT_EQ(1, mock_service.resume_upload_call_count());
+ EXPECT_EQ(0, mock_service.multipart_upload_call_count());
+ EXPECT_EQ(2 * 1024 * 1024, mock_service.received_bytes());
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_TRUE(upload_location.is_empty());
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
+ ASSERT_EQ(1U, upload_progress_values.size());
+ EXPECT_EQ(test_util::ProgressInfo(2 * 1024 * 1024, 2 * 1024 * 1024),
+ upload_progress_values[0]);
+}
+
+TEST_F(DriveUploaderTest, InitiateUploadFail) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 2 * 1024 * 1024, &local_path, &data));
+
+ DriveApiErrorCode error = HTTP_SUCCESS;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceNoConnectionAtInitiate mock_service;
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId, local_path, kTestMimeType,
+ UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_TRUE(upload_location.is_empty());
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(DriveUploaderTest, MultipartUploadFail) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(temp_dir_.path(), 512 * 1024,
+ &local_path, &data));
+
+ DriveApiErrorCode error = HTTP_SUCCESS;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceNoConnectionAtInitiate mock_service;
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId, local_path, kTestMimeType,
+ UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_TRUE(upload_location.is_empty());
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(DriveUploaderTest, InitiateUploadNoConflict) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 512 * 1024, &local_path, &data));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ UploadExistingFileOptions options;
+ options.etag = kTestETag;
+ uploader.UploadExistingFile(kTestInitiateUploadResourceId,
+ local_path,
+ kTestMimeType,
+ options,
+ test_util::CreateCopyResultCallback(
+ &error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(DriveUploaderTest, MultipartUploadConflict) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 512 * 1024, &local_path, &data));
+ const std::string kDestinationETag("destination_etag");
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ UploadExistingFileOptions options;
+ options.etag = kDestinationETag;
+ uploader.UploadExistingFile(kTestInitiateUploadResourceId,
+ local_path,
+ kTestMimeType,
+ options,
+ test_util::CreateCopyResultCallback(
+ &error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CONFLICT, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(DriveUploaderTest, InitiateUploadConflict) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 2 * 1024 * 1024, &local_path, &data));
+ const std::string kDestinationETag("destination_etag");
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ UploadExistingFileOptions options;
+ options.etag = kDestinationETag;
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId, local_path, kTestMimeType, options,
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CONFLICT, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(DriveUploaderTest, ResumeUploadFail) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 2 * 1024 * 1024, &local_path, &data));
+
+ DriveApiErrorCode error = HTTP_SUCCESS;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceNoConnectionAtResume mock_service;
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId, local_path, kTestMimeType,
+ UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_EQ(GURL(kTestUploadExistingFileURL), upload_location);
+}
+
+TEST_F(DriveUploaderTest, GetUploadStatusFail) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 2 * 1024 * 1024, &local_path, &data));
+
+ DriveApiErrorCode error = HTTP_SUCCESS;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceNoConnectionAtGetUploadStatus mock_service;
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ uploader.ResumeUploadFile(GURL(kTestUploadExistingFileURL),
+ local_path,
+ kTestMimeType,
+ test_util::CreateCopyResultCallback(
+ &error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(DriveUploaderTest, NonExistingSourceFile) {
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ DriveUploader uploader(NULL, // NULL, the service won't be used.
+ base::ThreadTaskRunnerHandle::Get().get());
+ uploader.UploadExistingFile(
+ kTestInitiateUploadResourceId,
+ temp_dir_.path().AppendASCII("_this_path_should_not_exist_"),
+ kTestMimeType, UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
+ google_apis::ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ // Should return failure without doing any attempt to connect to the server.
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(DriveUploaderTest, ResumeUpload) {
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), 1024 * 1024, &local_path, &data));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ scoped_ptr<FileResource> entry;
+
+ MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
+ DriveUploader uploader(&mock_service,
+ base::ThreadTaskRunnerHandle::Get().get());
+ // Emulate the situation that the only first part is successfully uploaded,
+ // but not the latter half.
+ mock_service.set_received_bytes(512 * 1024);
+
+ std::vector<test_util::ProgressInfo> upload_progress_values;
+ uploader.ResumeUploadFile(
+ GURL(kTestUploadExistingFileURL),
+ local_path,
+ kTestMimeType,
+ test_util::CreateCopyResultCallback(
+ &error, &upload_location, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(1, mock_service.resume_upload_call_count());
+ EXPECT_EQ(1024 * 1024, mock_service.received_bytes());
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_TRUE(upload_location.is_empty());
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
+ ASSERT_EQ(1U, upload_progress_values.size());
+ EXPECT_EQ(test_util::ProgressInfo(1024 * 1024, 1024 * 1024),
+ upload_progress_values[0]);
+}
+
+class MockDriveServiceForBatchProcessing : public DummyDriveService {
+ public:
+ struct UploadFileInfo {
+ enum { NEW_FILE, EXISTING_FILE } type;
+ std::string content_type;
+ uint64 content_length;
+ std::string parent_resource_id;
+ std::string resource_id;
+ std::string title;
+ base::FilePath local_file_path;
+ google_apis::FileResourceCallback callback;
+ google_apis::ProgressCallback progress_callback;
+ };
+
+ class BatchRequestConfigurator : public BatchRequestConfiguratorInterface {
+ public:
+ explicit BatchRequestConfigurator(
+ MockDriveServiceForBatchProcessing* service)
+ : service(service) {}
+
+ CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override {
+ UploadFileInfo info;
+ info.type = UploadFileInfo::NEW_FILE;
+ info.content_type = content_type;
+ info.content_length = content_length;
+ info.parent_resource_id = parent_resource_id;
+ info.title = title;
+ info.local_file_path = local_file_path;
+ info.callback = callback;
+ info.progress_callback = progress_callback;
+ service->files.push_back(info);
+ return CancelCallback();
+ }
+
+ CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override {
+ UploadFileInfo info;
+ info.type = UploadFileInfo::EXISTING_FILE;
+ info.content_type = content_type;
+ info.content_length = content_length;
+ info.resource_id = resource_id;
+ info.local_file_path = local_file_path;
+ info.callback = callback;
+ info.progress_callback = progress_callback;
+ service->files.push_back(info);
+ return CancelCallback();
+ }
+
+ void Commit() override {
+ ASSERT_FALSE(service->committed);
+ service->committed = true;
+ for (const auto& file : service->files) {
+ SendMultipartUploadResult(HTTP_SUCCESS, file.content_length,
+ file.callback, file.progress_callback);
+ }
+ }
+
+ private:
+ MockDriveServiceForBatchProcessing* service;
+ };
+
+ public:
+ scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() override {
+ committed = false;
+ return scoped_ptr<BatchRequestConfiguratorInterface>(
+ new BatchRequestConfigurator(this));
+ }
+
+ std::vector<UploadFileInfo> files;
+ bool committed;
+};
+
+TEST_F(DriveUploaderTest, BatchProcessing) {
+ // Preapre test file.
+ const size_t kTestFileSize = 1024 * 512;
+ base::FilePath local_path;
+ std::string data;
+ ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
+ temp_dir_.path(), kTestFileSize, &local_path, &data));
+
+ // Prepare test target.
+ MockDriveServiceForBatchProcessing service;
+ DriveUploader uploader(&service, base::ThreadTaskRunnerHandle::Get().get());
+
+ struct {
+ DriveApiErrorCode error;
+ GURL resume_url;
+ scoped_ptr<FileResource> file;
+ UploadCompletionCallback callback() {
+ return test_util::CreateCopyResultCallback(&error, &resume_url, &file);
+ }
+ } results[2];
+
+ uploader.StartBatchProcessing();
+ uploader.UploadNewFile("parent_resource_id", local_path, "title",
+ kTestMimeType, UploadNewFileOptions(),
+ results[0].callback(),
+ google_apis::ProgressCallback());
+ uploader.UploadExistingFile(
+ "resource_id", local_path, kTestMimeType, UploadExistingFileOptions(),
+ results[1].callback(), google_apis::ProgressCallback());
+ uploader.StopBatchProcessing();
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(2u, service.files.size());
+ EXPECT_TRUE(service.committed);
+
+ EXPECT_EQ(MockDriveServiceForBatchProcessing::UploadFileInfo::NEW_FILE,
+ service.files[0].type);
+ EXPECT_EQ(kTestMimeType, service.files[0].content_type);
+ EXPECT_EQ(kTestFileSize, service.files[0].content_length);
+ EXPECT_EQ("parent_resource_id", service.files[0].parent_resource_id);
+ EXPECT_EQ("", service.files[0].resource_id);
+ EXPECT_EQ("title", service.files[0].title);
+ EXPECT_EQ(local_path.value(), service.files[0].local_file_path.value());
+
+ EXPECT_EQ(MockDriveServiceForBatchProcessing::UploadFileInfo::EXISTING_FILE,
+ service.files[1].type);
+ EXPECT_EQ(kTestMimeType, service.files[1].content_type);
+ EXPECT_EQ(kTestFileSize, service.files[1].content_length);
+ EXPECT_EQ("", service.files[1].parent_resource_id);
+ EXPECT_EQ("resource_id", service.files[1].resource_id);
+ EXPECT_EQ("", service.files[1].title);
+ EXPECT_EQ(local_path.value(), service.files[1].local_file_path.value());
+
+ EXPECT_EQ(HTTP_SUCCESS, results[0].error);
+ EXPECT_TRUE(results[0].resume_url.is_empty());
+ EXPECT_TRUE(results[0].file);
+
+ EXPECT_EQ(HTTP_SUCCESS, results[1].error);
+ EXPECT_TRUE(results[1].resume_url.is_empty());
+ EXPECT_TRUE(results[1].file);
+}
+
+TEST_F(DriveUploaderTest, BatchProcessingWithError) {
+ // Prepare test target.
+ MockDriveServiceForBatchProcessing service;
+ DriveUploader uploader(&service, base::ThreadTaskRunnerHandle::Get().get());
+
+ struct {
+ DriveApiErrorCode error;
+ GURL resume_url;
+ scoped_ptr<FileResource> file;
+ UploadCompletionCallback callback() {
+ return test_util::CreateCopyResultCallback(&error, &resume_url, &file);
+ }
+ } results[2];
+
+ uploader.StartBatchProcessing();
+ uploader.UploadNewFile("parent_resource_id",
+ base::FilePath(FILE_PATH_LITERAL("/path/non_exists")),
+ "title", kTestMimeType, UploadNewFileOptions(),
+ results[0].callback(),
+ google_apis::ProgressCallback());
+ uploader.UploadExistingFile(
+ "resource_id", base::FilePath(FILE_PATH_LITERAL("/path/non_exists")),
+ kTestMimeType, UploadExistingFileOptions(), results[1].callback(),
+ google_apis::ProgressCallback());
+ uploader.StopBatchProcessing();
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(0u, service.files.size());
+ EXPECT_TRUE(service.committed);
+
+ EXPECT_EQ(HTTP_NOT_FOUND, results[0].error);
+ EXPECT_TRUE(results[0].resume_url.is_empty());
+ EXPECT_FALSE(results[0].file);
+
+ EXPECT_EQ(HTTP_NOT_FOUND, results[1].error);
+ EXPECT_TRUE(results[1].resume_url.is_empty());
+ EXPECT_FALSE(results[1].file);
+}
+} // namespace drive
diff --git a/components/drive/event_logger.cc b/components/drive/event_logger.cc
new file mode 100644
index 0000000..9815c11
--- /dev/null
+++ b/components/drive/event_logger.cc
@@ -0,0 +1,63 @@
+// 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 "components/drive/event_logger.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace drive {
+
+EventLogger::Event::Event(
+ int id, logging::LogSeverity severity, const std::string& what)
+ : id(id),
+ severity(severity),
+ when(base::Time::Now()),
+ what(what) {
+}
+
+EventLogger::EventLogger()
+ : history_size_(kDefaultHistorySize),
+ next_event_id_(0) {
+}
+
+EventLogger::~EventLogger() {
+}
+
+void EventLogger::LogRawString(logging::LogSeverity severity,
+ const std::string& what) {
+ base::AutoLock auto_lock(lock_);
+ history_.push_back(Event(next_event_id_, severity, what));
+ ++next_event_id_;
+ if (history_.size() > history_size_)
+ history_.pop_front();
+}
+
+void EventLogger::Log(logging::LogSeverity severity, const char* format, ...) {
+ std::string what;
+
+ va_list args;
+ va_start(args, format);
+ base::StringAppendV(&what, format, args);
+ va_end(args);
+
+ DVLOG(1) << what;
+ LogRawString(severity, what);
+}
+
+void EventLogger::SetHistorySize(size_t history_size) {
+ base::AutoLock auto_lock(lock_);
+ history_.clear();
+ history_size_ = history_size;
+}
+
+std::vector<EventLogger::Event> EventLogger::GetHistory() {
+ base::AutoLock auto_lock(lock_);
+ std::vector<Event> output;
+ output.assign(history_.begin(), history_.end());
+ return output;
+}
+
+
+} // namespace drive
diff --git a/components/drive/event_logger.h b/components/drive/event_logger.h
new file mode 100644
index 0000000..6b82a4c
--- /dev/null
+++ b/components/drive/event_logger.h
@@ -0,0 +1,69 @@
+// 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.
+
+#ifndef COMPONENTS_DRIVE_EVENT_LOGGER_H_
+#define COMPONENTS_DRIVE_EVENT_LOGGER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+
+namespace drive {
+
+// The default history size used by EventLogger.
+const int kDefaultHistorySize = 1000;
+
+// EventLogger is used to collect and expose text messages for diagnosing
+// behaviors of Google APIs stuff. For instance, the collected messages are
+// exposed to chrome:drive-internals.
+class EventLogger {
+ public:
+ // Represents a single event log.
+ struct Event {
+ Event(int id, logging::LogSeverity severity, const std::string& what);
+ int id; // Monotonically increasing ID starting from 0.
+ logging::LogSeverity severity; // Severity of the event.
+ base::Time when; // When the event occurred.
+ std::string what; // What happened.
+ };
+
+ // Creates an event logger that keeps the latest kDefaultHistorySize events.
+ EventLogger();
+ ~EventLogger();
+
+ // Logs a message and its severity.
+ // Can be called from any thread as long as the object is alive.
+ void LogRawString(logging::LogSeverity severity, const std::string& what);
+
+ // Logs a message with formatting.
+ // Can be called from any thread as long as the object is alive.
+ void Log(logging::LogSeverity severity, const char* format, ...)
+ PRINTF_FORMAT(3, 4);
+
+ // Sets the history size. The existing history is cleared.
+ // Can be called from any thread as long as the object is alive.
+ void SetHistorySize(size_t history_size);
+
+ // Gets the list of latest events (the oldest event comes first).
+ // Can be called from any thread as long as the object is alive.
+ std::vector<Event> GetHistory();
+
+ private:
+ std::deque<Event> history_; // guarded by lock_.
+ size_t history_size_; // guarded by lock_.
+ int next_event_id_; // guarded by lock_.
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventLogger);
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_EVENT_LOGGER_H_
diff --git a/components/drive/event_logger_unittest.cc b/components/drive/event_logger_unittest.cc
new file mode 100644
index 0000000..9d3b1fd
--- /dev/null
+++ b/components/drive/event_logger_unittest.cc
@@ -0,0 +1,43 @@
+// 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 "components/drive/event_logger.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+
+TEST(EventLoggerTest, BasicLogging) {
+ EventLogger logger;
+ logger.SetHistorySize(3); // At most 3 events are kept.
+ EXPECT_EQ(0U, logger.GetHistory().size());
+
+ logger.Log(logging::LOG_INFO, "first");
+ logger.Log(logging::LOG_INFO, "%dnd", 2);
+ logger.Log(logging::LOG_INFO, "third");
+
+ // Events are recorded in the chronological order with sequential IDs.
+ std::vector<EventLogger::Event> history = logger.GetHistory();
+ ASSERT_EQ(3U, history.size());
+ EXPECT_EQ(0, history[0].id);
+ EXPECT_EQ("first", history[0].what);
+ EXPECT_EQ(1, history[1].id);
+ EXPECT_EQ("2nd", history[1].what);
+ EXPECT_EQ(2, history[2].id);
+ EXPECT_EQ("third", history[2].what);
+
+ logger.Log(logging::LOG_INFO, "fourth");
+ // It does not log events beyond the specified.
+ history = logger.GetHistory();
+ ASSERT_EQ(3U, history.size());
+ // The oldest events is pushed out.
+ EXPECT_EQ(1, history[0].id);
+ EXPECT_EQ("2nd", history[0].what);
+ EXPECT_EQ(2, history[1].id);
+ EXPECT_EQ("third", history[1].what);
+ EXPECT_EQ(3, history[2].id);
+ EXPECT_EQ("fourth", history[2].what);
+}
+
+} // namespace drive
diff --git a/components/drive/service/drive_api_service.cc b/components/drive/service/drive_api_service.cc
new file mode 100644
index 0000000..644079a
--- /dev/null
+++ b/components/drive/service/drive_api_service.cc
@@ -0,0 +1,872 @@
+// 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 "components/drive/service/drive_api_service.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "components/drive/drive_api_util.h"
+#include "google_apis/drive/auth_service.h"
+#include "google_apis/drive/base_requests.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/drive_api_requests.h"
+#include "google_apis/drive/files_list_request_runner.h"
+#include "google_apis/drive/request_sender.h"
+#include "google_apis/google_api_keys.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using google_apis::AboutResourceCallback;
+using google_apis::AppList;
+using google_apis::AppListCallback;
+using google_apis::AuthStatusCallback;
+using google_apis::AuthorizeAppCallback;
+using google_apis::CancelCallback;
+using google_apis::ChangeList;
+using google_apis::ChangeListCallback;
+using google_apis::DownloadActionCallback;
+using google_apis::EntryActionCallback;
+using google_apis::FileList;
+using google_apis::FileListCallback;
+using google_apis::FileResource;
+using google_apis::FileResourceCallback;
+using google_apis::DRIVE_OTHER_ERROR;
+using google_apis::DRIVE_PARSE_ERROR;
+using google_apis::DriveApiErrorCode;
+using google_apis::GetContentCallback;
+using google_apis::GetShareUrlCallback;
+using google_apis::HTTP_NOT_IMPLEMENTED;
+using google_apis::HTTP_SUCCESS;
+using google_apis::InitiateUploadCallback;
+using google_apis::ProgressCallback;
+using google_apis::RequestSender;
+using google_apis::FilesListRequestRunner;
+using google_apis::UploadRangeResponse;
+using google_apis::drive::AboutGetRequest;
+using google_apis::drive::AppsListRequest;
+using google_apis::drive::ChangesListRequest;
+using google_apis::drive::ChangesListNextPageRequest;
+using google_apis::drive::ChildrenDeleteRequest;
+using google_apis::drive::ChildrenInsertRequest;
+using google_apis::drive::DownloadFileRequest;
+using google_apis::drive::FilesCopyRequest;
+using google_apis::drive::FilesGetRequest;
+using google_apis::drive::FilesInsertRequest;
+using google_apis::drive::FilesPatchRequest;
+using google_apis::drive::FilesListRequest;
+using google_apis::drive::FilesListNextPageRequest;
+using google_apis::drive::FilesDeleteRequest;
+using google_apis::drive::FilesTrashRequest;
+using google_apis::drive::GetUploadStatusRequest;
+using google_apis::drive::InitiateUploadExistingFileRequest;
+using google_apis::drive::InitiateUploadNewFileRequest;
+using google_apis::drive::ResumeUploadRequest;
+using google_apis::drive::UploadRangeCallback;
+
+namespace drive {
+
+namespace {
+
+// OAuth2 scopes for Drive API.
+const char kDriveScope[] = "https://www.googleapis.com/auth/drive";
+const char kDriveAppsReadonlyScope[] =
+ "https://www.googleapis.com/auth/drive.apps.readonly";
+const char kDriveAppsScope[] = "https://www.googleapis.com/auth/drive.apps";
+const char kDocsListScope[] = "https://docs.google.com/feeds/";
+
+// Mime type to create a directory.
+const char kFolderMimeType[] = "application/vnd.google-apps.folder";
+
+// Max number of file entries to be fetched in a single http request.
+//
+// The larger the number is,
+// - The total running time to fetch the whole file list will become shorter.
+// - The running time for a single request tends to become longer.
+// Since the file list fetching is a completely background task, for our side,
+// only the total time matters. However, the server seems to have a time limit
+// per single request, which disables us to set the largest value (1000).
+// TODO(kinaba): make it larger when the server gets faster.
+const int kMaxNumFilesResourcePerRequest = 300;
+const int kMaxNumFilesResourcePerRequestForSearch = 100;
+
+// For performance, we declare all fields we use.
+const char kAboutResourceFields[] =
+ "kind,quotaBytesTotal,quotaBytesUsedAggregate,largestChangeId,rootFolderId";
+const char kFileResourceFields[] =
+ "kind,id,title,createdDate,sharedWithMeDate,mimeType,"
+ "md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width,"
+ "imageMediaMetadata/height,imageMediaMetadata/rotation,etag,"
+ "parents(id,parentLink),alternateLink,"
+ "modifiedDate,lastViewedByMeDate,shared";
+const char kFileResourceOpenWithLinksFields[] =
+ "kind,id,openWithLinks/*";
+const char kFileResourceShareLinkFields[] =
+ "kind,id,shareLink";
+const char kFileListFields[] =
+ "kind,items(kind,id,title,createdDate,sharedWithMeDate,"
+ "mimeType,md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width,"
+ "imageMediaMetadata/height,imageMediaMetadata/rotation,etag,"
+ "parents(id,parentLink),alternateLink,"
+ "modifiedDate,lastViewedByMeDate,shared),nextLink";
+const char kChangeListFields[] =
+ "kind,items(file(kind,id,title,createdDate,sharedWithMeDate,"
+ "mimeType,md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width,"
+ "imageMediaMetadata/height,imageMediaMetadata/rotation,etag,"
+ "parents(id,parentLink),alternateLink,modifiedDate,"
+ "lastViewedByMeDate,shared),deleted,id,fileId,modificationDate),nextLink,"
+ "largestChangeId";
+
+void ExtractOpenUrlAndRun(const std::string& app_id,
+ const AuthorizeAppCallback& callback,
+ DriveApiErrorCode error,
+ scoped_ptr<FileResource> value) {
+ DCHECK(!callback.is_null());
+
+ if (!value) {
+ callback.Run(error, GURL());
+ return;
+ }
+
+ const std::vector<FileResource::OpenWithLink>& open_with_links =
+ value->open_with_links();
+ for (size_t i = 0; i < open_with_links.size(); ++i) {
+ if (open_with_links[i].app_id == app_id) {
+ callback.Run(HTTP_SUCCESS, open_with_links[i].open_url);
+ return;
+ }
+ }
+
+ // Not found.
+ callback.Run(DRIVE_OTHER_ERROR, GURL());
+}
+
+void ExtractShareUrlAndRun(const GetShareUrlCallback& callback,
+ DriveApiErrorCode error,
+ scoped_ptr<FileResource> value) {
+ callback.Run(error, value ? value->share_link() : GURL());
+}
+
+// Ignores the |entry|, and runs the |callback|.
+void EntryActionCallbackAdapter(
+ const EntryActionCallback& callback,
+ DriveApiErrorCode error, scoped_ptr<FileResource> entry) {
+ callback.Run(error);
+}
+
+// The resource ID for the root directory for Drive API is defined in the spec:
+// https://developers.google.com/drive/folder
+const char kDriveApiRootDirectoryResourceId[] = "root";
+
+} // namespace
+
+BatchRequestConfigurator::BatchRequestConfigurator(
+ const base::WeakPtr<google_apis::drive::BatchUploadRequest>& batch_request,
+ base::SequencedTaskRunner* task_runner,
+ const google_apis::DriveApiUrlGenerator& url_generator,
+ const google_apis::CancelCallback& cancel_callback)
+ : batch_request_(batch_request),
+ task_runner_(task_runner),
+ url_generator_(url_generator),
+ cancel_callback_(cancel_callback) {
+}
+
+BatchRequestConfigurator::~BatchRequestConfigurator() {
+ // The batch requst has not been committed.
+ if (batch_request_)
+ cancel_callback_.Run();
+}
+
+google_apis::CancelCallback BatchRequestConfigurator::MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ scoped_ptr<google_apis::BatchableDelegate> delegate(
+ new google_apis::drive::MultipartUploadNewFileDelegate(
+ task_runner_.get(), title, parent_resource_id, content_type,
+ content_length, options.modified_date, options.last_viewed_by_me_date,
+ local_file_path, options.properties, url_generator_, callback,
+ progress_callback));
+ // Batch request can be null when pre-authorization for the requst is failed
+ // in request sender.
+ if (batch_request_)
+ batch_request_->AddRequest(delegate.release());
+ else
+ delegate->NotifyError(DRIVE_OTHER_ERROR);
+ return cancel_callback_;
+}
+
+google_apis::CancelCallback
+BatchRequestConfigurator::MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ scoped_ptr<google_apis::BatchableDelegate> delegate(
+ new google_apis::drive::MultipartUploadExistingFileDelegate(
+ task_runner_.get(), options.title, resource_id,
+ options.parent_resource_id, content_type, content_length,
+ options.modified_date, options.last_viewed_by_me_date,
+ local_file_path, options.etag, options.properties, url_generator_,
+ callback, progress_callback));
+ // Batch request can be null when pre-authorization for the requst is failed
+ // in request sender.
+ if (batch_request_)
+ batch_request_->AddRequest(delegate.release());
+ else
+ delegate->NotifyError(DRIVE_OTHER_ERROR);
+ return cancel_callback_;
+}
+
+void BatchRequestConfigurator::Commit() {
+ DCHECK(CalledOnValidThread());
+ if (!batch_request_)
+ return;
+ batch_request_->Commit();
+ batch_request_.reset();
+}
+
+DriveAPIService::DriveAPIService(
+ OAuth2TokenService* oauth2_token_service,
+ net::URLRequestContextGetter* url_request_context_getter,
+ base::SequencedTaskRunner* blocking_task_runner,
+ const GURL& base_url,
+ const GURL& base_download_url,
+ const std::string& custom_user_agent)
+ : oauth2_token_service_(oauth2_token_service),
+ url_request_context_getter_(url_request_context_getter),
+ blocking_task_runner_(blocking_task_runner),
+ url_generator_(base_url, base_download_url),
+ custom_user_agent_(custom_user_agent) {
+}
+
+DriveAPIService::~DriveAPIService() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (sender_.get())
+ sender_->auth_service()->RemoveObserver(this);
+}
+
+void DriveAPIService::Initialize(const std::string& account_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::vector<std::string> scopes;
+ scopes.push_back(kDriveScope);
+ scopes.push_back(kDriveAppsReadonlyScope);
+ scopes.push_back(kDriveAppsScope);
+
+ // Note: The following scope is used to support GetShareUrl on Drive API v2.
+ // Unfortunately, there is no support on Drive API v2, so we need to fall back
+ // to GData WAPI for the GetShareUrl.
+ scopes.push_back(kDocsListScope);
+
+ sender_.reset(new RequestSender(
+ new google_apis::AuthService(oauth2_token_service_,
+ account_id,
+ url_request_context_getter_.get(),
+ scopes),
+ url_request_context_getter_.get(),
+ blocking_task_runner_.get(),
+ custom_user_agent_));
+ sender_->auth_service()->AddObserver(this);
+
+ files_list_request_runner_.reset(
+ new FilesListRequestRunner(sender_.get(), url_generator_));
+}
+
+void DriveAPIService::AddObserver(DriveServiceObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DriveAPIService::RemoveObserver(DriveServiceObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+bool DriveAPIService::CanSendRequest() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ return HasRefreshToken();
+}
+
+std::string DriveAPIService::GetRootResourceId() const {
+ return kDriveApiRootDirectoryResourceId;
+}
+
+CancelCallback DriveAPIService::GetAllFileList(
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FilesListRequest* request = new FilesListRequest(
+ sender_.get(), url_generator_, callback);
+ request->set_max_results(kMaxNumFilesResourcePerRequest);
+ request->set_q("trashed = false"); // Exclude trashed files.
+ request->set_fields(kFileListFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetFileListInDirectory(
+ const std::string& directory_resource_id,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!directory_resource_id.empty());
+ DCHECK(!callback.is_null());
+
+ // Because children.list method on Drive API v2 returns only the list of
+ // children's references, but we need all file resource list.
+ // So, here we use files.list method instead, with setting parents query.
+ // After the migration from GData WAPI to Drive API v2, we should clean the
+ // code up by moving the responsibility to include "parents" in the query
+ // to client side.
+ // We aren't interested in files in trash in this context, neither.
+ return files_list_request_runner_->CreateAndStartWithSizeBackoff(
+ kMaxNumFilesResourcePerRequest,
+ base::StringPrintf(
+ "'%s' in parents and trashed = false",
+ util::EscapeQueryStringValue(directory_resource_id).c_str()),
+ kFileListFields, callback);
+}
+
+CancelCallback DriveAPIService::Search(
+ const std::string& search_query,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!search_query.empty());
+ DCHECK(!callback.is_null());
+
+ return files_list_request_runner_->CreateAndStartWithSizeBackoff(
+ kMaxNumFilesResourcePerRequestForSearch,
+ util::TranslateQuery(search_query), kFileListFields, callback);
+}
+
+CancelCallback DriveAPIService::SearchByTitle(
+ const std::string& title,
+ const std::string& directory_resource_id,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!title.empty());
+ DCHECK(!callback.is_null());
+
+ std::string query;
+ base::StringAppendF(&query, "title = '%s'",
+ util::EscapeQueryStringValue(title).c_str());
+ if (!directory_resource_id.empty()) {
+ base::StringAppendF(
+ &query, " and '%s' in parents",
+ util::EscapeQueryStringValue(directory_resource_id).c_str());
+ }
+ query += " and trashed = false";
+
+ FilesListRequest* request = new FilesListRequest(
+ sender_.get(), url_generator_, callback);
+ request->set_max_results(kMaxNumFilesResourcePerRequest);
+ request->set_q(query);
+ request->set_fields(kFileListFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetChangeList(
+ int64 start_changestamp,
+ const ChangeListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ ChangesListRequest* request = new ChangesListRequest(
+ sender_.get(), url_generator_, callback);
+ request->set_max_results(kMaxNumFilesResourcePerRequest);
+ request->set_start_change_id(start_changestamp);
+ request->set_fields(kChangeListFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetRemainingChangeList(
+ const GURL& next_link,
+ const ChangeListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!next_link.is_empty());
+ DCHECK(!callback.is_null());
+
+ ChangesListNextPageRequest* request = new ChangesListNextPageRequest(
+ sender_.get(), callback);
+ request->set_next_link(next_link);
+ request->set_fields(kChangeListFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetRemainingFileList(
+ const GURL& next_link,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!next_link.is_empty());
+ DCHECK(!callback.is_null());
+
+ FilesListNextPageRequest* request = new FilesListNextPageRequest(
+ sender_.get(), callback);
+ request->set_next_link(next_link);
+ request->set_fields(kFileListFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetFileResource(
+ const std::string& resource_id,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FilesGetRequest* request = new FilesGetRequest(
+ sender_.get(), url_generator_, google_apis::IsGoogleChromeAPIKeyUsed(),
+ callback);
+ request->set_file_id(resource_id);
+ request->set_fields(kFileResourceFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetShareUrl(
+ const std::string& resource_id,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (!google_apis::IsGoogleChromeAPIKeyUsed()) {
+ LOG(ERROR) << "Only the official build of Chrome OS can open share dialogs "
+ << "from the file manager.";
+ }
+
+ FilesGetRequest* request = new FilesGetRequest(
+ sender_.get(), url_generator_, google_apis::IsGoogleChromeAPIKeyUsed(),
+ base::Bind(&ExtractShareUrlAndRun, callback));
+ request->set_file_id(resource_id);
+ request->set_fields(kFileResourceShareLinkFields);
+ request->set_embed_origin(embed_origin);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetAboutResource(
+ const AboutResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ AboutGetRequest* request =
+ new AboutGetRequest(sender_.get(), url_generator_, callback);
+ request->set_fields(kAboutResourceFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::GetAppList(const AppListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ return sender_->StartRequestWithAuthRetry(
+ new AppsListRequest(sender_.get(), url_generator_,
+ google_apis::IsGoogleChromeAPIKeyUsed(), callback));
+}
+
+CancelCallback DriveAPIService::DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const DownloadActionCallback& download_action_callback,
+ const GetContentCallback& get_content_callback,
+ const ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!download_action_callback.is_null());
+ // get_content_callback may be null.
+
+ return sender_->StartRequestWithAuthRetry(new DownloadFileRequest(
+ sender_.get(), url_generator_, resource_id, local_cache_path,
+ download_action_callback, get_content_callback, progress_callback));
+}
+
+CancelCallback DriveAPIService::DeleteResource(
+ const std::string& resource_id,
+ const std::string& etag,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FilesDeleteRequest* request = new FilesDeleteRequest(
+ sender_.get(), url_generator_, callback);
+ request->set_file_id(resource_id);
+ request->set_etag(etag);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::TrashResource(
+ const std::string& resource_id,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FilesTrashRequest* request = new FilesTrashRequest(
+ sender_.get(), url_generator_,
+ base::Bind(&EntryActionCallbackAdapter, callback));
+ request->set_file_id(resource_id);
+ request->set_fields(kFileResourceFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::AddNewDirectory(
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FilesInsertRequest* request = new FilesInsertRequest(
+ sender_.get(), url_generator_, callback);
+ request->set_last_viewed_by_me_date(options.last_viewed_by_me_date);
+ request->set_mime_type(kFolderMimeType);
+ request->set_modified_date(options.modified_date);
+ request->add_parent(parent_resource_id);
+ request->set_title(directory_title);
+ request->set_properties(options.properties);
+ request->set_fields(kFileResourceFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::CopyResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FilesCopyRequest* request = new FilesCopyRequest(
+ sender_.get(), url_generator_, callback);
+ request->set_file_id(resource_id);
+ request->add_parent(parent_resource_id);
+ request->set_title(new_title);
+ request->set_modified_date(last_modified);
+ request->set_fields(kFileResourceFields);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::UpdateResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const base::Time& last_viewed_by_me,
+ const google_apis::drive::Properties& properties,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FilesPatchRequest* request = new FilesPatchRequest(
+ sender_.get(), url_generator_, callback);
+ request->set_file_id(resource_id);
+ request->set_title(new_title);
+ if (!parent_resource_id.empty())
+ request->add_parent(parent_resource_id);
+ if (!last_modified.is_null()) {
+ // Need to set setModifiedDate to true to overwrite modifiedDate.
+ request->set_set_modified_date(true);
+ request->set_modified_date(last_modified);
+ }
+ if (!last_viewed_by_me.is_null()) {
+ // Need to set updateViewedDate to false, otherwise the lastViewedByMeDate
+ // will be set to the request time (not the specified time via request).
+ request->set_update_viewed_date(false);
+ request->set_last_viewed_by_me_date(last_viewed_by_me);
+ }
+ request->set_fields(kFileResourceFields);
+ request->set_properties(properties);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::AddResourceToDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ ChildrenInsertRequest* request =
+ new ChildrenInsertRequest(sender_.get(), url_generator_, callback);
+ request->set_folder_id(parent_resource_id);
+ request->set_id(resource_id);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::RemoveResourceFromDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ ChildrenDeleteRequest* request =
+ new ChildrenDeleteRequest(sender_.get(), url_generator_, callback);
+ request->set_child_id(resource_id);
+ request->set_folder_id(parent_resource_id);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const InitiateUploadCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ InitiateUploadNewFileRequest* request =
+ new InitiateUploadNewFileRequest(sender_.get(),
+ url_generator_,
+ content_type,
+ content_length,
+ parent_resource_id,
+ title,
+ callback);
+ request->set_modified_date(options.modified_date);
+ request->set_last_viewed_by_me_date(options.last_viewed_by_me_date);
+ request->set_properties(options.properties);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const InitiateUploadCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ InitiateUploadExistingFileRequest* request =
+ new InitiateUploadExistingFileRequest(sender_.get(),
+ url_generator_,
+ content_type,
+ content_length,
+ resource_id,
+ options.etag,
+ callback);
+ request->set_parent_resource_id(options.parent_resource_id);
+ request->set_title(options.title);
+ request->set_modified_date(options.modified_date);
+ request->set_last_viewed_by_me_date(options.last_viewed_by_me_date);
+ request->set_properties(options.properties);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+CancelCallback DriveAPIService::ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const UploadRangeCallback& callback,
+ const ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ return sender_->StartRequestWithAuthRetry(new ResumeUploadRequest(
+ sender_.get(), upload_url, start_position, end_position, content_length,
+ content_type, local_file_path, callback, progress_callback));
+}
+
+CancelCallback DriveAPIService::GetUploadStatus(
+ const GURL& upload_url,
+ int64 content_length,
+ const UploadRangeCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ return sender_->StartRequestWithAuthRetry(new GetUploadStatusRequest(
+ sender_.get(), upload_url, content_length, callback));
+}
+
+CancelCallback DriveAPIService::MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const drive::UploadNewFileOptions& options,
+ const FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ return sender_->StartRequestWithAuthRetry(
+ new google_apis::drive::SingleBatchableDelegateRequest(
+ sender_.get(),
+ new google_apis::drive::MultipartUploadNewFileDelegate(
+ sender_->blocking_task_runner(), title, parent_resource_id,
+ content_type, content_length, options.modified_date,
+ options.last_viewed_by_me_date, local_file_path,
+ options.properties, url_generator_, callback,
+ progress_callback)));
+}
+
+CancelCallback DriveAPIService::MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const drive::UploadExistingFileOptions& options,
+ const FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ return sender_->StartRequestWithAuthRetry(
+ new google_apis::drive::SingleBatchableDelegateRequest(
+ sender_.get(),
+ new google_apis::drive::MultipartUploadExistingFileDelegate(
+ sender_->blocking_task_runner(), options.title, resource_id,
+ options.parent_resource_id, content_type, content_length,
+ options.modified_date, options.last_viewed_by_me_date,
+ local_file_path, options.etag, options.properties, url_generator_,
+ callback, progress_callback)));
+}
+
+CancelCallback DriveAPIService::AuthorizeApp(
+ const std::string& resource_id,
+ const std::string& app_id,
+ const AuthorizeAppCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ // Files.Authorize is only available for whitelisted clients like official
+ // Google Chrome. In other cases, we fall back to Files.Get that returns the
+ // same value as Files.Authorize without doing authorization. In that case,
+ // the app can open if it was authorized by other means (from whitelisted
+ // clients or drive.google.com web UI.)
+ if (google_apis::IsGoogleChromeAPIKeyUsed()) {
+ google_apis::drive::FilesAuthorizeRequest* request =
+ new google_apis::drive::FilesAuthorizeRequest(
+ sender_.get(), url_generator_,
+ base::Bind(&ExtractOpenUrlAndRun, app_id, callback));
+ request->set_app_id(app_id);
+ request->set_file_id(resource_id);
+ request->set_fields(kFileResourceOpenWithLinksFields);
+ return sender_->StartRequestWithAuthRetry(request);
+ } else {
+ FilesGetRequest* request = new FilesGetRequest(
+ sender_.get(), url_generator_, google_apis::IsGoogleChromeAPIKeyUsed(),
+ base::Bind(&ExtractOpenUrlAndRun, app_id, callback));
+ request->set_file_id(resource_id);
+ request->set_fields(kFileResourceOpenWithLinksFields);
+ return sender_->StartRequestWithAuthRetry(request);
+ }
+}
+
+CancelCallback DriveAPIService::UninstallApp(
+ const std::string& app_id,
+ const google_apis::EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ google_apis::drive::AppsDeleteRequest* request =
+ new google_apis::drive::AppsDeleteRequest(sender_.get(), url_generator_,
+ callback);
+ request->set_app_id(app_id);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+google_apis::CancelCallback DriveAPIService::AddPermission(
+ const std::string& resource_id,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const google_apis::EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ google_apis::drive::PermissionsInsertRequest* request =
+ new google_apis::drive::PermissionsInsertRequest(sender_.get(),
+ url_generator_,
+ callback);
+ request->set_id(resource_id);
+ request->set_role(role);
+ request->set_type(google_apis::drive::PERMISSION_TYPE_USER);
+ request->set_value(email);
+ return sender_->StartRequestWithAuthRetry(request);
+}
+
+bool DriveAPIService::HasAccessToken() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return sender_->auth_service()->HasAccessToken();
+}
+
+void DriveAPIService::RequestAccessToken(const AuthStatusCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ const std::string access_token = sender_->auth_service()->access_token();
+ if (!access_token.empty()) {
+ callback.Run(google_apis::HTTP_NOT_MODIFIED, access_token);
+ return;
+ }
+
+ // Retrieve the new auth token.
+ sender_->auth_service()->StartAuthentication(callback);
+}
+
+bool DriveAPIService::HasRefreshToken() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return sender_->auth_service()->HasRefreshToken();
+}
+
+void DriveAPIService::ClearAccessToken() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ sender_->auth_service()->ClearAccessToken();
+}
+
+void DriveAPIService::ClearRefreshToken() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ sender_->auth_service()->ClearRefreshToken();
+}
+
+void DriveAPIService::OnOAuth2RefreshTokenChanged() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (CanSendRequest()) {
+ FOR_EACH_OBSERVER(
+ DriveServiceObserver, observers_, OnReadyToSendRequests());
+ } else if (!HasRefreshToken()) {
+ FOR_EACH_OBSERVER(
+ DriveServiceObserver, observers_, OnRefreshTokenInvalid());
+ }
+}
+
+scoped_ptr<BatchRequestConfiguratorInterface>
+DriveAPIService::StartBatchRequest() {
+ scoped_ptr<google_apis::drive::BatchUploadRequest> request(
+ new google_apis::drive::BatchUploadRequest(sender_.get(),
+ url_generator_));
+ const base::WeakPtr<google_apis::drive::BatchUploadRequest> weak_ref =
+ request->GetWeakPtrAsBatchUploadRequest();
+ // Have sender_ manage the lifetime of the request.
+ // TODO(hirono): Currently we need to pass the ownership of the request to
+ // RequestSender before the request is committed because the request has a
+ // reference to RequestSender and we should ensure to delete the request when
+ // the sender is deleted. Resolve the circulating dependency and fix it.
+ const google_apis::CancelCallback callback =
+ sender_->StartRequestWithAuthRetry(request.release());
+ return make_scoped_ptr<BatchRequestConfiguratorInterface>(
+ new BatchRequestConfigurator(weak_ref, sender_->blocking_task_runner(),
+ url_generator_, callback));
+}
+
+} // namespace drive
diff --git a/components/drive/service/drive_api_service.h b/components/drive/service/drive_api_service.h
new file mode 100644
index 0000000..22bb7d4
--- /dev/null
+++ b/components/drive/service/drive_api_service.h
@@ -0,0 +1,269 @@
+// 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.
+
+#ifndef COMPONENTS_DRIVE_SERVICE_DRIVE_API_SERVICE_H_
+#define COMPONENTS_DRIVE_SERVICE_DRIVE_API_SERVICE_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/service/drive_service_interface.h"
+#include "google_apis/drive/auth_service_interface.h"
+#include "google_apis/drive/auth_service_observer.h"
+#include "google_apis/drive/drive_api_url_generator.h"
+
+class GURL;
+class OAuth2TokenService;
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace google_apis {
+class FilesListRequestRunner;
+class RequestSender;
+namespace drive {
+class BatchUploadRequest;
+} // namespace drive
+} // namespace google_apis
+
+namespace net {
+class URLRequestContextGetter;
+} // namespace net
+
+namespace drive {
+
+// Builder for batch request returned by |DriveAPIService|.
+class BatchRequestConfigurator : public BatchRequestConfiguratorInterface,
+ public base::NonThreadSafe {
+ public:
+ BatchRequestConfigurator(
+ const base::WeakPtr<google_apis::drive::BatchUploadRequest>&
+ batch_request,
+ base::SequencedTaskRunner* task_runner,
+ const google_apis::DriveApiUrlGenerator& url_generator,
+ const google_apis::CancelCallback& cancel_callback);
+ ~BatchRequestConfigurator() override;
+
+ // BatchRequestConfiguratorInterface overrides.
+ google_apis::CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ void Commit() override;
+
+ private:
+ // Reference to batch request. It turns to null after committing.
+ base::WeakPtr<google_apis::drive::BatchUploadRequest> batch_request_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ google_apis::DriveApiUrlGenerator url_generator_;
+ google_apis::CancelCallback cancel_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(BatchRequestConfigurator);
+};
+
+// This class provides Drive request calls using Drive V2 API.
+// Details of API call are abstracted in each request class and this class
+// works as a thin wrapper for the API.
+class DriveAPIService : public DriveServiceInterface,
+ public google_apis::AuthServiceObserver {
+ public:
+ // |oauth2_token_service| is used for obtaining OAuth2 access tokens.
+ // |url_request_context_getter| is used to initialize URLFetcher.
+ // |blocking_task_runner| is used to run blocking tasks (like parsing JSON).
+ // |base_url| is used to generate URLs for communication with the drive API.
+ // |base_download_url| is used to generate URLs for downloading file from the
+ // drive API.
+ // |custom_user_agent| will be used for the User-Agent header in HTTP
+ // requests issues through the service if the value is not empty.
+ DriveAPIService(
+ OAuth2TokenService* oauth2_token_service,
+ net::URLRequestContextGetter* url_request_context_getter,
+ base::SequencedTaskRunner* blocking_task_runner,
+ const GURL& base_url,
+ const GURL& base_download_url,
+ const std::string& custom_user_agent);
+ ~DriveAPIService() override;
+
+ // DriveServiceInterface Overrides
+ void Initialize(const std::string& account_id) override;
+ void AddObserver(DriveServiceObserver* observer) override;
+ void RemoveObserver(DriveServiceObserver* observer) override;
+ bool CanSendRequest() const override;
+ bool HasAccessToken() const override;
+ void RequestAccessToken(
+ const google_apis::AuthStatusCallback& callback) override;
+ bool HasRefreshToken() const override;
+ void ClearAccessToken() override;
+ void ClearRefreshToken() override;
+ std::string GetRootResourceId() const override;
+ google_apis::CancelCallback GetAllFileList(
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetFileListInDirectory(
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback Search(
+ const std::string& search_query,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback SearchByTitle(
+ const std::string& title,
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetChangeList(
+ int64 start_changestamp,
+ const google_apis::ChangeListCallback& callback) override;
+ google_apis::CancelCallback GetRemainingChangeList(
+ const GURL& next_link,
+ const google_apis::ChangeListCallback& callback) override;
+ google_apis::CancelCallback GetRemainingFileList(
+ const GURL& next_link,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetFileResource(
+ const std::string& resource_id,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback GetShareUrl(
+ const std::string& resource_id,
+ const GURL& embed_origin,
+ const google_apis::GetShareUrlCallback& callback) override;
+ google_apis::CancelCallback GetAboutResource(
+ const google_apis::AboutResourceCallback& callback) override;
+ google_apis::CancelCallback GetAppList(
+ const google_apis::AppListCallback& callback) override;
+ google_apis::CancelCallback DeleteResource(
+ const std::string& resource_id,
+ const std::string& etag,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback TrashResource(
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const google_apis::DownloadActionCallback& download_action_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback CopyResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback UpdateResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const base::Time& last_viewed_by_me,
+ const google_apis::drive::Properties& properties,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback AddResourceToDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback RemoveResourceFromDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback AddNewDirectory(
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) override;
+ google_apis::CancelCallback InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) override;
+ google_apis::CancelCallback ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const google_apis::drive::UploadRangeCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback GetUploadStatus(
+ const GURL& upload_url,
+ int64 content_length,
+ const google_apis::drive::UploadRangeCallback& callback) override;
+ google_apis::CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const drive::UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const drive::UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback AuthorizeApp(
+ const std::string& resource_id,
+ const std::string& app_id,
+ const google_apis::AuthorizeAppCallback& callback) override;
+ google_apis::CancelCallback UninstallApp(
+ const std::string& app_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback AddPermission(
+ const std::string& resource_id,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const google_apis::EntryActionCallback& callback) override;
+ scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() override;
+
+ private:
+ // AuthServiceObserver override.
+ void OnOAuth2RefreshTokenChanged() override;
+
+ // The class is expected to run on UI thread.
+ base::ThreadChecker thread_checker_;
+
+ OAuth2TokenService* oauth2_token_service_;
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ scoped_ptr<google_apis::RequestSender> sender_;
+ scoped_ptr<google_apis::FilesListRequestRunner> files_list_request_runner_;
+ base::ObserverList<DriveServiceObserver> observers_;
+ google_apis::DriveApiUrlGenerator url_generator_;
+ const std::string custom_user_agent_;
+
+ DISALLOW_COPY_AND_ASSIGN(DriveAPIService);
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SERVICE_DRIVE_API_SERVICE_H_
diff --git a/components/drive/service/drive_api_service_unittest.cc b/components/drive/service/drive_api_service_unittest.cc
new file mode 100644
index 0000000..6655e68
--- /dev/null
+++ b/components/drive/service/drive_api_service_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright 2015 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 "base/message_loop/message_loop.h"
+#include "base/test/test_simple_task_runner.h"
+#include "components/drive/service/drive_api_service.h"
+#include "google_apis/drive/dummy_auth_service.h"
+#include "google_apis/drive/request_sender.h"
+#include "google_apis/drive/test_util.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace {
+const char kTestUserAgent[] = "test-user-agent";
+}
+
+class TestAuthService : public google_apis::DummyAuthService {
+ public:
+ void StartAuthentication(
+ const google_apis::AuthStatusCallback& callback) override {
+ callback_ = callback;
+ }
+
+ bool HasAccessToken() const override { return false; }
+
+ void SendHttpError() {
+ ASSERT_FALSE(callback_.is_null());
+ callback_.Run(google_apis::HTTP_UNAUTHORIZED, "");
+ }
+
+ private:
+ google_apis::AuthStatusCallback callback_;
+};
+
+TEST(DriveAPIServiceTest, BatchRequestConfiguratorWithAuthFailure) {
+ const GURL test_base_url("http://localhost/");
+ google_apis::DriveApiUrlGenerator url_generator(test_base_url, test_base_url);
+ scoped_refptr<base::TestSimpleTaskRunner> task_runner =
+ new base::TestSimpleTaskRunner();
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
+ new net::TestURLRequestContextGetter(task_runner.get());
+ google_apis::RequestSender sender(new TestAuthService,
+ request_context_getter.get(),
+ task_runner.get(), kTestUserAgent);
+ google_apis::drive::BatchUploadRequest* const request =
+ new google_apis::drive::BatchUploadRequest(&sender, url_generator);
+ sender.StartRequestWithAuthRetry(request);
+ BatchRequestConfigurator configurator(
+ request->GetWeakPtrAsBatchUploadRequest(), task_runner.get(),
+ url_generator, google_apis::CancelCallback());
+ static_cast<TestAuthService*>(sender.auth_service())->SendHttpError();
+
+ {
+ google_apis::DriveApiErrorCode error = google_apis::HTTP_SUCCESS;
+ scoped_ptr<google_apis::FileResource> file_resource;
+ configurator.MultipartUploadNewFile(
+ "text/plain", 10, "", "title",
+ base::FilePath(FILE_PATH_LITERAL("/file")), UploadNewFileOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&error,
+ &file_resource),
+ google_apis::ProgressCallback());
+ EXPECT_EQ(google_apis::DRIVE_OTHER_ERROR, error);
+ }
+ {
+ google_apis::DriveApiErrorCode error = google_apis::HTTP_SUCCESS;
+ scoped_ptr<google_apis::FileResource> file_resource;
+ configurator.MultipartUploadExistingFile(
+ "text/plain", 10, "resource_id",
+ base::FilePath(FILE_PATH_LITERAL("/file")), UploadExistingFileOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&error,
+ &file_resource),
+ google_apis::ProgressCallback());
+ EXPECT_EQ(google_apis::DRIVE_OTHER_ERROR, error);
+ }
+}
+
+} // namespace drive
diff --git a/components/drive/service/drive_service_interface.cc b/components/drive/service/drive_service_interface.cc
new file mode 100644
index 0000000..76c1f20
--- /dev/null
+++ b/components/drive/service/drive_service_interface.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 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 "components/drive/service/drive_service_interface.h"
+
+namespace drive {
+
+AddNewDirectoryOptions::AddNewDirectoryOptions() {
+}
+
+AddNewDirectoryOptions::~AddNewDirectoryOptions() {
+}
+
+UploadNewFileOptions::UploadNewFileOptions() {
+}
+
+UploadNewFileOptions::~UploadNewFileOptions() {
+}
+
+UploadExistingFileOptions::UploadExistingFileOptions() {
+}
+
+UploadExistingFileOptions::~UploadExistingFileOptions() {
+}
+
+} // namespace drive
diff --git a/components/drive/service/drive_service_interface.h b/components/drive/service/drive_service_interface.h
new file mode 100644
index 0000000..1c0dadd
--- /dev/null
+++ b/components/drive/service/drive_service_interface.h
@@ -0,0 +1,467 @@
+// 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.
+
+#ifndef COMPONENTS_DRIVE_SERVICE_DRIVE_SERVICE_INTERFACE_H_
+#define COMPONENTS_DRIVE_SERVICE_DRIVE_SERVICE_INTERFACE_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "google_apis/drive/auth_service_interface.h"
+#include "google_apis/drive/base_requests.h"
+#include "google_apis/drive/drive_api_requests.h"
+#include "google_apis/drive/drive_common_callbacks.h"
+
+namespace base {
+class Time;
+}
+
+namespace drive {
+
+// Observer interface for DriveServiceInterface.
+class DriveServiceObserver {
+ public:
+ // Triggered when the service gets ready to send requests.
+ virtual void OnReadyToSendRequests() {}
+
+ // Called when the refresh token was found to be invalid.
+ virtual void OnRefreshTokenInvalid() {}
+
+ protected:
+ virtual ~DriveServiceObserver() {}
+};
+
+// Optional parameters for AddNewDirectory().
+struct AddNewDirectoryOptions {
+ AddNewDirectoryOptions();
+ ~AddNewDirectoryOptions();
+
+ // modified_date of the directory.
+ // Pass the null Time if you are not interested in setting this property.
+ base::Time modified_date;
+
+ // last_viewed_by_me_date of the directory.
+ // Pass the null Time if you are not interested in setting this property.
+ base::Time last_viewed_by_me_date;
+
+ // List of properties for a new directory.
+ google_apis::drive::Properties properties;
+};
+
+// Optional parameters for InitiateUploadNewFile() and
+// MultipartUploadNewFile().
+struct UploadNewFileOptions {
+ UploadNewFileOptions();
+ ~UploadNewFileOptions();
+
+ // modified_date of the file.
+ // Pass the null Time if you are not interested in setting this property.
+ base::Time modified_date;
+
+ // last_viewed_by_me_date of the file.
+ // Pass the null Time if you are not interested in setting this property.
+ base::Time last_viewed_by_me_date;
+
+ // List of properties for a new file.
+ google_apis::drive::Properties properties;
+};
+
+// Optional parameters for InitiateUploadExistingFile() and
+// MultipartUploadExistingFile().
+struct UploadExistingFileOptions {
+ UploadExistingFileOptions();
+ ~UploadExistingFileOptions();
+
+ // Expected ETag of the file. UPLOAD_ERROR_CONFLICT error is generated when
+ // matching fails.
+ // Pass the empty string to disable this behavior.
+ std::string etag;
+
+ // New parent of the file.
+ // Pass the empty string to keep the property unchanged.
+ std::string parent_resource_id;
+
+ // New title of the file.
+ // Pass the empty string to keep the property unchanged.
+ std::string title;
+
+ // New modified_date of the file.
+ // Pass the null Time if you are not interested in setting this property.
+ base::Time modified_date;
+
+ // New last_viewed_by_me_date of the file.
+ // Pass the null Time if you are not interested in setting this property.
+ base::Time last_viewed_by_me_date;
+
+ // List of new properties for an existing file (it will be merged with
+ // existing properties).
+ google_apis::drive::Properties properties;
+};
+
+// Interface where we define operations that can be sent in batch requests.
+class DriveServiceBatchOperationsInterface {
+ public:
+ virtual ~DriveServiceBatchOperationsInterface() {}
+
+ // Uploads a file by a single request with multipart body. It's more efficient
+ // for small files than using |InitiateUploadNewFile| and |ResumeUpload|.
+ // |content_type| and |content_length| should be the ones of the file to be
+ // uploaded. |callback| must not be null. |progress_callback| may be null.
+ virtual google_apis::CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) = 0;
+
+ // Uploads a file by a single request with multipart body. It's more efficient
+ // for small files than using |InitiateUploadExistingFile| and |ResumeUpload|.
+ // |content_type| and |content_length| should be the ones of the file to be
+ // uploaded. |callback| must not be null. |progress_callback| may be null.
+ virtual google_apis::CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) = 0;
+};
+
+// Builder returned by DriveServiceInterface to build batch request.
+class BatchRequestConfiguratorInterface
+ : public DriveServiceBatchOperationsInterface {
+ public:
+ ~BatchRequestConfiguratorInterface() override {}
+
+ // Commits and sends the batch request.
+ virtual void Commit() = 0;
+};
+
+// This defines an interface for sharing by DriveService and MockDriveService
+// so that we can do testing of clients of DriveService.
+//
+// All functions must be called on UI thread. DriveService is built on top of
+// URLFetcher that runs on UI thread.
+class DriveServiceInterface : public DriveServiceBatchOperationsInterface {
+ public:
+ ~DriveServiceInterface() override {}
+
+ // Common service:
+
+ // Initializes the documents service with |account_id|.
+ virtual void Initialize(const std::string& account_id) = 0;
+
+ // Adds an observer.
+ virtual void AddObserver(DriveServiceObserver* observer) = 0;
+
+ // Removes an observer.
+ virtual void RemoveObserver(DriveServiceObserver* observer) = 0;
+
+ // True if ready to send requests.
+ virtual bool CanSendRequest() const = 0;
+
+ // Authentication service:
+
+ // True if OAuth2 access token is retrieved and believed to be fresh.
+ virtual bool HasAccessToken() const = 0;
+
+ // Gets the cached OAuth2 access token or if empty, then fetches a new one.
+ virtual void RequestAccessToken(
+ const google_apis::AuthStatusCallback& callback) = 0;
+
+ // True if OAuth2 refresh token is present.
+ virtual bool HasRefreshToken() const = 0;
+
+ // Clears OAuth2 access token.
+ virtual void ClearAccessToken() = 0;
+
+ // Clears OAuth2 refresh token.
+ virtual void ClearRefreshToken() = 0;
+
+ // Document access:
+
+ // Returns the resource id for the root directory.
+ virtual std::string GetRootResourceId() const = 0;
+
+ // Fetches a file list of the account. |callback| will be called upon
+ // completion.
+ // If the list is too long, it may be paged. In such a case, a URL to fetch
+ // remaining results will be included in the returned result. See also
+ // GetRemainingFileList.
+ //
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback GetAllFileList(
+ const google_apis::FileListCallback& callback) = 0;
+
+ // Fetches a file list in the directory with |directory_resource_id|.
+ // |callback| will be called upon completion.
+ // If the list is too long, it may be paged. In such a case, a URL to fetch
+ // remaining results will be included in the returned result. See also
+ // GetRemainingFileList.
+ //
+ // |directory_resource_id| must not be empty.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback GetFileListInDirectory(
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) = 0;
+
+ // Searches the resources for the |search_query| from all the user's
+ // resources. |callback| will be called upon completion.
+ // If the list is too long, it may be paged. In such a case, a URL to fetch
+ // remaining results will be included in the returned result. See also
+ // GetRemainingFileList.
+ //
+ // |search_query| must not be empty.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback Search(
+ const std::string& search_query,
+ const google_apis::FileListCallback& callback) = 0;
+
+ // Searches the resources with the |title|.
+ // |directory_resource_id| is an optional parameter. If it is empty,
+ // the search target is all the existing resources. Otherwise, it is
+ // the resources directly under the directory with |directory_resource_id|.
+ // If the list is too long, it may be paged. In such a case, a URL to fetch
+ // remaining results will be included in the returned result. See also
+ // GetRemainingFileList.
+ //
+ // |title| must not be empty, and |callback| must not be null.
+ virtual google_apis::CancelCallback SearchByTitle(
+ const std::string& title,
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) = 0;
+
+ // Fetches change list since |start_changestamp|. |callback| will be
+ // called upon completion.
+ // If the list is too long, it may be paged. In such a case, a URL to fetch
+ // remaining results will be included in the returned result. See also
+ // GetRemainingChangeList.
+ //
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback GetChangeList(
+ int64 start_changestamp,
+ const google_apis::ChangeListCallback& callback) = 0;
+
+ // The result of GetChangeList() may be paged.
+ // In such a case, a next link to fetch remaining result is returned.
+ // The page token can be used for this method. |callback| will be called upon
+ // completion.
+ //
+ // |next_link| must not be empty. |callback| must not be null.
+ virtual google_apis::CancelCallback GetRemainingChangeList(
+ const GURL& next_link,
+ const google_apis::ChangeListCallback& callback) = 0;
+
+ // The result of GetAllFileList(), GetFileListInDirectory(), Search()
+ // and SearchByTitle() may be paged. In such a case, a next link to fetch
+ // remaining result is returned. The page token can be used for this method.
+ // |callback| will be called upon completion.
+ //
+ // |next_link| must not be empty. |callback| must not be null.
+ virtual google_apis::CancelCallback GetRemainingFileList(
+ const GURL& next_link,
+ const google_apis::FileListCallback& callback) = 0;
+
+ // Fetches single entry metadata from server. The entry's file id equals
+ // |resource_id|.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback GetFileResource(
+ const std::string& resource_id,
+ const google_apis::FileResourceCallback& callback) = 0;
+
+ // Fetches an url for the sharing dialog for a single entry with id
+ // |resource_id|, to be embedded in a webview or an iframe with origin
+ // |embed_origin|. The url is returned via |callback| with results on the
+ // calling thread. |callback| must not be null.
+ virtual google_apis::CancelCallback GetShareUrl(
+ const std::string& resource_id,
+ const GURL& embed_origin,
+ const google_apis::GetShareUrlCallback& callback) = 0;
+
+ // Gets the about resource information from the server.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback GetAboutResource(
+ const google_apis::AboutResourceCallback& callback) = 0;
+
+ // Gets the application information from the server.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback GetAppList(
+ const google_apis::AppListCallback& callback) = 0;
+
+ // Permanently deletes a resource identified by its |resource_id|.
+ // If |etag| is not empty and did not match, the deletion fails with
+ // HTTP_PRECONDITION error.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback DeleteResource(
+ const std::string& resource_id,
+ const std::string& etag,
+ const google_apis::EntryActionCallback& callback) = 0;
+
+ // Trashes a resource identified by its |resource_id|.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback TrashResource(
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) = 0;
+
+ // Makes a copy of a resource with |resource_id|.
+ // The new resource will be put under a directory with |parent_resource_id|,
+ // and it'll be named |new_title|.
+ // If |last_modified| is not null, the modified date of the resource on the
+ // server will be set to the date.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback CopyResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const google_apis::FileResourceCallback& callback) = 0;
+
+ // Updates a resource with |resource_id| to the directory of
+ // |parent_resource_id| with renaming to |new_title|.
+ // If |last_modified| or |last_accessed| is not null, the modified/accessed
+ // date of the resource on the server will be set to the date.
+ // If |properties| are specified, then they will be set on |resource_id|.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback UpdateResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const base::Time& last_viewed_by_me,
+ const google_apis::drive::Properties& properties,
+ const google_apis::FileResourceCallback& callback) = 0;
+
+ // Adds a resource (document, file, or collection) identified by its
+ // |resource_id| to a collection represented by the |parent_resource_id|.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback AddResourceToDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) = 0;
+
+ // Removes a resource (document, file, collection) identified by its
+ // |resource_id| from a collection represented by the |parent_resource_id|.
+ // Upon completion, invokes |callback| with results on the calling thread.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback RemoveResourceFromDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) = 0;
+
+ // Adds new collection with |directory_title| under parent directory
+ // identified with |parent_resource_id|. |parent_resource_id| can be the
+ // value returned by GetRootResourceId to represent the root directory.
+ // Upon completion, invokes |callback| and passes newly created entry on
+ // the calling thread.
+ // This function cannot be named as "CreateDirectory" as it conflicts with
+ // a macro on Windows.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback AddNewDirectory(
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const google_apis::FileResourceCallback& callback) = 0;
+
+ // Downloads a file with |resourced_id|. The downloaded file will
+ // be stored at |local_cache_path| location. Upon completion, invokes
+ // |download_action_callback| with results on the calling thread.
+ // If |get_content_callback| is not empty,
+ // URLFetcherDelegate::OnURLFetchDownloadData will be called, which will in
+ // turn invoke |get_content_callback| on the calling thread.
+ // If |progress_callback| is not empty, it is invoked periodically when
+ // the download made some progress.
+ //
+ // |download_action_callback| must not be null.
+ // |get_content_callback| and |progress_callback| may be null.
+ virtual google_apis::CancelCallback DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const google_apis::DownloadActionCallback& download_action_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const google_apis::ProgressCallback& progress_callback) = 0;
+
+ // Initiates uploading of a new document/file.
+ // |content_type| and |content_length| should be the ones of the file to be
+ // uploaded.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) = 0;
+
+ // Initiates uploading of an existing document/file.
+ // |content_type| and |content_length| should be the ones of the file to be
+ // uploaded.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) = 0;
+
+ // Resumes uploading of a document/file on the calling thread.
+ // |callback| must not be null. |progress_callback| may be null.
+ virtual google_apis::CancelCallback ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const google_apis::drive::UploadRangeCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) = 0;
+
+ // Gets the current status of the uploading to |upload_url| from the server.
+ // |drive_file_path| and |content_length| should be set to the same value
+ // which is used for ResumeUpload.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback GetUploadStatus(
+ const GURL& upload_url,
+ int64 content_length,
+ const google_apis::drive::UploadRangeCallback& callback) = 0;
+
+ // Authorizes a Drive app with the id |app_id| to open the given file.
+ // Upon completion, invokes |callback| with the link to open the file with
+ // the provided app. |callback| must not be null.
+ virtual google_apis::CancelCallback AuthorizeApp(
+ const std::string& resource_id,
+ const std::string& app_id,
+ const google_apis::AuthorizeAppCallback& callback) = 0;
+
+ // Uninstalls a Drive app with the id |app_id|. |callback| must not be null.
+ virtual google_apis::CancelCallback UninstallApp(
+ const std::string& app_id,
+ const google_apis::EntryActionCallback& callback) = 0;
+
+ // Authorizes the account |email| to access |resource_id| as a |role|.
+ // |callback| must not be null.
+ virtual google_apis::CancelCallback AddPermission(
+ const std::string& resource_id,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const google_apis::EntryActionCallback& callback) = 0;
+
+ // Starts batch request and returns |BatchRequestConfigurator|.
+ virtual scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() = 0;
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SERVICE_DRIVE_SERVICE_INTERFACE_H_
diff --git a/components/drive/service/dummy_drive_service.cc b/components/drive/service/dummy_drive_service.cc
new file mode 100644
index 0000000..8700fce
--- /dev/null
+++ b/components/drive/service/dummy_drive_service.cc
@@ -0,0 +1,224 @@
+// 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 "components/drive/service/dummy_drive_service.h"
+
+#include "base/bind.h"
+
+using google_apis::AboutResourceCallback;
+using google_apis::AppListCallback;
+using google_apis::AuthStatusCallback;
+using google_apis::AuthorizeAppCallback;
+using google_apis::CancelCallback;
+using google_apis::ChangeListCallback;
+using google_apis::DownloadActionCallback;
+using google_apis::EntryActionCallback;
+using google_apis::FileListCallback;
+using google_apis::FileResourceCallback;
+using google_apis::GetContentCallback;
+using google_apis::GetShareUrlCallback;
+using google_apis::InitiateUploadCallback;
+using google_apis::ProgressCallback;
+using google_apis::drive::UploadRangeCallback;
+
+namespace drive {
+
+DummyDriveService::DummyDriveService() {}
+
+DummyDriveService::~DummyDriveService() {}
+
+void DummyDriveService::Initialize(const std::string& account_id) {}
+
+void DummyDriveService::AddObserver(DriveServiceObserver* observer) {}
+
+void DummyDriveService::RemoveObserver(DriveServiceObserver* observer) {}
+
+bool DummyDriveService::CanSendRequest() const { return true; }
+
+bool DummyDriveService::HasAccessToken() const { return true; }
+
+void DummyDriveService::RequestAccessToken(const AuthStatusCallback& callback) {
+ callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
+}
+
+bool DummyDriveService::HasRefreshToken() const { return true; }
+
+void DummyDriveService::ClearAccessToken() { }
+
+void DummyDriveService::ClearRefreshToken() { }
+
+std::string DummyDriveService::GetRootResourceId() const {
+ return "dummy_root";
+}
+
+CancelCallback DummyDriveService::GetAllFileList(
+ const FileListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetFileListInDirectory(
+ const std::string& directory_resource_id,
+ const FileListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::Search(
+ const std::string& search_query,
+ const FileListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::SearchByTitle(
+ const std::string& title,
+ const std::string& directory_resource_id,
+ const FileListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetChangeList(
+ int64 start_changestamp,
+ const ChangeListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetRemainingChangeList(
+ const GURL& next_link,
+ const ChangeListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetRemainingFileList(
+ const GURL& next_link,
+ const FileListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetFileResource(
+ const std::string& resource_id,
+ const FileResourceCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetShareUrl(
+ const std::string& resource_id,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetAboutResource(
+ const AboutResourceCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetAppList(
+ const AppListCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::DeleteResource(
+ const std::string& resource_id,
+ const std::string& etag,
+ const EntryActionCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::TrashResource(
+ const std::string& resource_id,
+ const EntryActionCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const DownloadActionCallback& download_action_callback,
+ const GetContentCallback& get_content_callback,
+ const ProgressCallback& progress_callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::CopyResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const FileResourceCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::UpdateResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const base::Time& last_viewed_by_me,
+ const google_apis::drive::Properties& properties,
+ const google_apis::FileResourceCallback& callback) {
+ return CancelCallback();
+}
+
+CancelCallback DummyDriveService::AddResourceToDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const EntryActionCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::RemoveResourceFromDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const EntryActionCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::AddNewDirectory(
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const FileResourceCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const InitiateUploadCallback& callback) {
+ return CancelCallback();
+}
+
+CancelCallback DummyDriveService::InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const InitiateUploadCallback& callback) {
+ return CancelCallback();
+}
+
+CancelCallback DummyDriveService::ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const UploadRangeCallback& callback,
+ const ProgressCallback& progress_callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::GetUploadStatus(
+ const GURL& upload_url,
+ int64 content_length,
+ const UploadRangeCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const FileResourceCallback& callback,
+ const ProgressCallback& progress_callback) {
+ return CancelCallback();
+}
+
+CancelCallback DummyDriveService::MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const FileResourceCallback& callback,
+ const ProgressCallback& progress_callback) {
+ return CancelCallback();
+}
+
+CancelCallback DummyDriveService::AuthorizeApp(
+ const std::string& resource_id,
+ const std::string& app_id,
+ const AuthorizeAppCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::UninstallApp(
+ const std::string& app_id,
+ const EntryActionCallback& callback) { return CancelCallback(); }
+
+CancelCallback DummyDriveService::AddPermission(
+ const std::string& resource_id,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const EntryActionCallback& callback) { return CancelCallback(); }
+scoped_ptr<BatchRequestConfiguratorInterface>
+DummyDriveService::StartBatchRequest() {
+ return scoped_ptr<BatchRequestConfiguratorInterface>();
+}
+
+} // namespace drive
diff --git a/components/drive/service/dummy_drive_service.h b/components/drive/service/dummy_drive_service.h
new file mode 100644
index 0000000..3317c45
--- /dev/null
+++ b/components/drive/service/dummy_drive_service.h
@@ -0,0 +1,166 @@
+// 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.
+
+#ifndef COMPONENTS_DRIVE_SERVICE_DUMMY_DRIVE_SERVICE_H_
+#define COMPONENTS_DRIVE_SERVICE_DUMMY_DRIVE_SERVICE_H_
+
+#include <string>
+
+#include "components/drive/service/drive_service_interface.h"
+#include "google_apis/drive/auth_service_interface.h"
+
+namespace drive {
+
+// Dummy implementation of DriveServiceInterface.
+// All functions do nothing, or return place holder values like 'true'.
+class DummyDriveService : public DriveServiceInterface {
+ public:
+ DummyDriveService();
+ ~DummyDriveService() override;
+
+ // DriveServiceInterface Overrides
+ void Initialize(const std::string& account_id) override;
+ void AddObserver(DriveServiceObserver* observer) override;
+ void RemoveObserver(DriveServiceObserver* observer) override;
+ bool CanSendRequest() const override;
+ bool HasAccessToken() const override;
+ void RequestAccessToken(
+ const google_apis::AuthStatusCallback& callback) override;
+ bool HasRefreshToken() const override;
+ void ClearAccessToken() override;
+ void ClearRefreshToken() override;
+ std::string GetRootResourceId() const override;
+ google_apis::CancelCallback GetAllFileList(
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetFileListInDirectory(
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback Search(
+ const std::string& search_query,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback SearchByTitle(
+ const std::string& title,
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetChangeList(
+ int64 start_changestamp,
+ const google_apis::ChangeListCallback& callback) override;
+ google_apis::CancelCallback GetRemainingChangeList(
+ const GURL& next_link,
+ const google_apis::ChangeListCallback& callback) override;
+ google_apis::CancelCallback GetRemainingFileList(
+ const GURL& next_link,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetFileResource(
+ const std::string& resource_id,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback GetShareUrl(
+ const std::string& resource_id,
+ const GURL& embed_origin,
+ const google_apis::GetShareUrlCallback& callback) override;
+ google_apis::CancelCallback GetAboutResource(
+ const google_apis::AboutResourceCallback& callback) override;
+ google_apis::CancelCallback GetAppList(
+ const google_apis::AppListCallback& callback) override;
+ google_apis::CancelCallback DeleteResource(
+ const std::string& resource_id,
+ const std::string& etag,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback TrashResource(
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const google_apis::DownloadActionCallback& download_action_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback CopyResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback UpdateResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const base::Time& last_viewed_by_me,
+ const google_apis::drive::Properties& properties,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback AddResourceToDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback RemoveResourceFromDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback AddNewDirectory(
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) override;
+ google_apis::CancelCallback InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) override;
+ google_apis::CancelCallback ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const google_apis::drive::UploadRangeCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback GetUploadStatus(
+ const GURL& upload_url,
+ int64 content_length,
+ const google_apis::drive::UploadRangeCallback& callback) override;
+ google_apis::CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback AuthorizeApp(
+ const std::string& resource_id,
+ const std::string& app_id,
+ const google_apis::AuthorizeAppCallback& callback) override;
+ google_apis::CancelCallback UninstallApp(
+ const std::string& app_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback AddPermission(
+ const std::string& resource_id,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const google_apis::EntryActionCallback& callback) override;
+ scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() override;
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SERVICE_DUMMY_DRIVE_SERVICE_H_
diff --git a/components/drive/service/fake_drive_service.cc b/components/drive/service/fake_drive_service.cc
new file mode 100644
index 0000000..81b55d1
--- /dev/null
+++ b/components/drive/service/fake_drive_service.cc
@@ -0,0 +1,1787 @@
+// 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 "components/drive/service/fake_drive_service.h"
+
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/drive/drive_api_util.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+#include "net/base/escape.h"
+#include "net/base/url_util.h"
+
+using google_apis::AboutResource;
+using google_apis::AboutResourceCallback;
+using google_apis::AppList;
+using google_apis::AppListCallback;
+using google_apis::AuthStatusCallback;
+using google_apis::AuthorizeAppCallback;
+using google_apis::CancelCallback;
+using google_apis::ChangeList;
+using google_apis::ChangeListCallback;
+using google_apis::ChangeResource;
+using google_apis::DownloadActionCallback;
+using google_apis::EntryActionCallback;
+using google_apis::FileList;
+using google_apis::FileListCallback;
+using google_apis::FileResource;
+using google_apis::FileResourceCallback;
+using google_apis::DRIVE_FILE_ERROR;
+using google_apis::DRIVE_NO_CONNECTION;
+using google_apis::DRIVE_OTHER_ERROR;
+using google_apis::DriveApiErrorCode;
+using google_apis::GetContentCallback;
+using google_apis::GetShareUrlCallback;
+using google_apis::HTTP_BAD_REQUEST;
+using google_apis::HTTP_CREATED;
+using google_apis::HTTP_FORBIDDEN;
+using google_apis::HTTP_NOT_FOUND;
+using google_apis::HTTP_NO_CONTENT;
+using google_apis::HTTP_PRECONDITION;
+using google_apis::HTTP_RESUME_INCOMPLETE;
+using google_apis::HTTP_SUCCESS;
+using google_apis::InitiateUploadCallback;
+using google_apis::ParentReference;
+using google_apis::ProgressCallback;
+using google_apis::UploadRangeResponse;
+using google_apis::drive::UploadRangeCallback;
+namespace test_util = google_apis::test_util;
+
+namespace drive {
+namespace {
+
+// Returns true if the entry matches with the search query.
+// Supports queries consist of following format.
+// - Phrases quoted by double/single quotes
+// - AND search for multiple words/phrases segmented by space
+// - Limited attribute search. Only "title:" is supported.
+bool EntryMatchWithQuery(const ChangeResource& entry,
+ const std::string& query) {
+ base::StringTokenizer tokenizer(query, " ");
+ tokenizer.set_quote_chars("\"'");
+ while (tokenizer.GetNext()) {
+ std::string key, value;
+ const std::string& token = tokenizer.token();
+ if (token.find(':') == std::string::npos) {
+ base::TrimString(token, "\"'", &value);
+ } else {
+ base::StringTokenizer key_value(token, ":");
+ key_value.set_quote_chars("\"'");
+ if (!key_value.GetNext())
+ return false;
+ key = key_value.token();
+ if (!key_value.GetNext())
+ return false;
+ base::TrimString(key_value.token(), "\"'", &value);
+ }
+
+ // TODO(peria): Deal with other attributes than title.
+ if (!key.empty() && key != "title")
+ return false;
+ // Search query in the title.
+ if (!entry.file() ||
+ entry.file()->title().find(value) == std::string::npos)
+ return false;
+ }
+ return true;
+}
+
+void ScheduleUploadRangeCallback(const UploadRangeCallback& callback,
+ int64 start_position,
+ int64 end_position,
+ DriveApiErrorCode error,
+ scoped_ptr<FileResource> entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ UploadRangeResponse(error,
+ start_position,
+ end_position),
+ base::Passed(&entry)));
+}
+
+void FileListCallbackAdapter(const FileListCallback& callback,
+ DriveApiErrorCode error,
+ scoped_ptr<ChangeList> change_list) {
+ scoped_ptr<FileList> file_list;
+ if (!change_list) {
+ callback.Run(error, file_list.Pass());
+ return;
+ }
+
+ file_list.reset(new FileList);
+ file_list->set_next_link(change_list->next_link());
+ for (size_t i = 0; i < change_list->items().size(); ++i) {
+ const ChangeResource& entry = *change_list->items()[i];
+ if (entry.file())
+ file_list->mutable_items()->push_back(new FileResource(*entry.file()));
+ }
+ callback.Run(error, file_list.Pass());
+}
+
+bool UserHasWriteAccess(google_apis::drive::PermissionRole user_permission) {
+ switch (user_permission) {
+ case google_apis::drive::PERMISSION_ROLE_OWNER:
+ case google_apis::drive::PERMISSION_ROLE_WRITER:
+ return true;
+ case google_apis::drive::PERMISSION_ROLE_READER:
+ case google_apis::drive::PERMISSION_ROLE_COMMENTER:
+ break;
+ }
+ return false;
+}
+
+void CallFileResouceCallback(const FileResourceCallback& callback,
+ const UploadRangeResponse& response,
+ scoped_ptr<FileResource> entry) {
+ callback.Run(response.code, entry.Pass());
+}
+
+struct CallResumeUpload {
+ CallResumeUpload() {}
+ ~CallResumeUpload() {}
+
+ void Run(DriveApiErrorCode code, const GURL& upload_url) {
+ if (service) {
+ service->ResumeUpload(
+ upload_url,
+ /* start position */ 0,
+ /* end position */ content_length,
+ content_length,
+ content_type,
+ local_file_path,
+ base::Bind(&CallFileResouceCallback, callback),
+ progress_callback);
+ }
+ }
+
+ base::WeakPtr<FakeDriveService> service;
+ int64 content_length;
+ std::string content_type;
+ base::FilePath local_file_path;
+ FileResourceCallback callback;
+ ProgressCallback progress_callback;
+};
+
+} // namespace
+
+struct FakeDriveService::EntryInfo {
+ EntryInfo() : user_permission(google_apis::drive::PERMISSION_ROLE_OWNER) {}
+
+ google_apis::ChangeResource change_resource;
+ GURL share_url;
+ std::string content_data;
+
+ // Behaves in the same way as "userPermission" described in
+ // https://developers.google.com/drive/v2/reference/files
+ google_apis::drive::PermissionRole user_permission;
+};
+
+struct FakeDriveService::UploadSession {
+ std::string content_type;
+ int64 content_length;
+ std::string parent_resource_id;
+ std::string resource_id;
+ std::string etag;
+ std::string title;
+
+ int64 uploaded_size;
+
+ UploadSession()
+ : content_length(0),
+ uploaded_size(0) {}
+
+ UploadSession(
+ std::string content_type,
+ int64 content_length,
+ std::string parent_resource_id,
+ std::string resource_id,
+ std::string etag,
+ std::string title)
+ : content_type(content_type),
+ content_length(content_length),
+ parent_resource_id(parent_resource_id),
+ resource_id(resource_id),
+ etag(etag),
+ title(title),
+ uploaded_size(0) {
+ }
+};
+
+FakeDriveService::FakeDriveService()
+ : about_resource_(new AboutResource),
+ published_date_seq_(0),
+ next_upload_sequence_number_(0),
+ default_max_results_(0),
+ resource_id_count_(0),
+ file_list_load_count_(0),
+ change_list_load_count_(0),
+ directory_load_count_(0),
+ about_resource_load_count_(0),
+ app_list_load_count_(0),
+ blocked_file_list_load_count_(0),
+ offline_(false),
+ never_return_all_file_list_(false),
+ share_url_base_("https://share_url/"),
+ weak_ptr_factory_(this) {
+ about_resource_->set_largest_change_id(654321);
+ about_resource_->set_quota_bytes_total(9876543210);
+ about_resource_->set_quota_bytes_used_aggregate(6789012345);
+ about_resource_->set_root_folder_id(GetRootResourceId());
+}
+
+FakeDriveService::~FakeDriveService() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ STLDeleteValues(&entries_);
+}
+
+bool FakeDriveService::LoadAppListForDriveApi(
+ const std::string& relative_path) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Load JSON data, which must be a dictionary.
+ scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path);
+ CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+ app_info_value_.reset(
+ static_cast<base::DictionaryValue*>(value.release()));
+ return app_info_value_;
+}
+
+void FakeDriveService::AddApp(const std::string& app_id,
+ const std::string& app_name,
+ const std::string& product_id,
+ const std::string& create_url,
+ bool is_removable) {
+ if (app_json_template_.empty()) {
+ base::FilePath path =
+ test_util::GetTestFilePath("drive/applist_app_template.json");
+ CHECK(base::ReadFileToString(path, &app_json_template_));
+ }
+
+ std::string app_json = app_json_template_;
+ base::ReplaceSubstringsAfterOffset(&app_json, 0, "$AppId", app_id);
+ base::ReplaceSubstringsAfterOffset(&app_json, 0, "$AppName", app_name);
+ base::ReplaceSubstringsAfterOffset(&app_json, 0, "$ProductId", product_id);
+ base::ReplaceSubstringsAfterOffset(&app_json, 0, "$CreateUrl", create_url);
+ base::ReplaceSubstringsAfterOffset(
+ &app_json, 0, "$Removable", is_removable ? "true" : "false");
+
+ JSONStringValueDeserializer json(app_json);
+ std::string error_message;
+ scoped_ptr<base::Value> value(json.Deserialize(NULL, &error_message));
+ CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
+
+ base::ListValue* item_list;
+ CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
+ item_list->Append(value.release());
+}
+
+void FakeDriveService::RemoveAppByProductId(const std::string& product_id) {
+ base::ListValue* item_list;
+ CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
+ for (size_t i = 0; i < item_list->GetSize(); ++i) {
+ base::DictionaryValue* item;
+ CHECK(item_list->GetDictionary(i, &item));
+ const char kKeyProductId[] = "productId";
+ std::string item_product_id;
+ if (item->GetStringWithoutPathExpansion(kKeyProductId, &item_product_id) &&
+ product_id == item_product_id) {
+ item_list->Remove(i, NULL);
+ return;
+ }
+ }
+}
+
+bool FakeDriveService::HasApp(const std::string& app_id) const {
+ base::ListValue* item_list;
+ CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
+ for (size_t i = 0; i < item_list->GetSize(); ++i) {
+ base::DictionaryValue* item;
+ CHECK(item_list->GetDictionary(i, &item));
+ const char kKeyId[] = "id";
+ std::string item_id;
+ if (item->GetStringWithoutPathExpansion(kKeyId, &item_id) &&
+ item_id == app_id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void FakeDriveService::SetQuotaValue(int64 used, int64 total) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ about_resource_->set_quota_bytes_used_aggregate(used);
+ about_resource_->set_quota_bytes_total(total);
+}
+
+GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
+ return GURL("https://fake_server/" + net::EscapePath(resource_id));
+}
+
+void FakeDriveService::Initialize(const std::string& account_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+bool FakeDriveService::CanSendRequest() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return true;
+}
+
+bool FakeDriveService::HasAccessToken() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return true;
+}
+
+void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
+}
+
+bool FakeDriveService::HasRefreshToken() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return true;
+}
+
+void FakeDriveService::ClearAccessToken() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void FakeDriveService::ClearRefreshToken() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+std::string FakeDriveService::GetRootResourceId() const {
+ return "fake_root";
+}
+
+CancelCallback FakeDriveService::GetAllFileList(
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (never_return_all_file_list_) {
+ ++blocked_file_list_load_count_;
+ return CancelCallback();
+ }
+
+ GetChangeListInternal(0, // start changestamp
+ std::string(), // empty search query
+ std::string(), // no directory resource id,
+ 0, // start offset
+ default_max_results_,
+ &file_list_load_count_,
+ base::Bind(&FileListCallbackAdapter, callback));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetFileListInDirectory(
+ const std::string& directory_resource_id,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!directory_resource_id.empty());
+ DCHECK(!callback.is_null());
+
+ GetChangeListInternal(0, // start changestamp
+ std::string(), // empty search query
+ directory_resource_id,
+ 0, // start offset
+ default_max_results_,
+ &directory_load_count_,
+ base::Bind(&FileListCallbackAdapter, callback));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::Search(
+ const std::string& search_query,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!search_query.empty());
+ DCHECK(!callback.is_null());
+
+ GetChangeListInternal(0, // start changestamp
+ search_query,
+ std::string(), // no directory resource id,
+ 0, // start offset
+ default_max_results_,
+ NULL,
+ base::Bind(&FileListCallbackAdapter, callback));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::SearchByTitle(
+ const std::string& title,
+ const std::string& directory_resource_id,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!title.empty());
+ DCHECK(!callback.is_null());
+
+ // Note: the search implementation here doesn't support quotation unescape,
+ // so don't escape here.
+ GetChangeListInternal(0, // start changestamp
+ base::StringPrintf("title:'%s'", title.c_str()),
+ directory_resource_id,
+ 0, // start offset
+ default_max_results_,
+ NULL,
+ base::Bind(&FileListCallbackAdapter, callback));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetChangeList(
+ int64 start_changestamp,
+ const ChangeListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ GetChangeListInternal(start_changestamp,
+ std::string(), // empty search query
+ std::string(), // no directory resource id,
+ 0, // start offset
+ default_max_results_,
+ &change_list_load_count_,
+ callback);
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetRemainingChangeList(
+ const GURL& next_link,
+ const ChangeListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!next_link.is_empty());
+ DCHECK(!callback.is_null());
+
+ // "changestamp", "q", "parent" and "start-offset" are parameters to
+ // implement "paging" of the result on FakeDriveService.
+ // The URL should be the one filled in GetChangeListInternal of the
+ // previous method invocation, so it should start with "http://localhost/?".
+ // See also GetChangeListInternal.
+ DCHECK_EQ(next_link.host(), "localhost");
+ DCHECK_EQ(next_link.path(), "/");
+
+ int64 start_changestamp = 0;
+ std::string search_query;
+ std::string directory_resource_id;
+ int start_offset = 0;
+ int max_results = default_max_results_;
+ base::StringPairs parameters;
+ if (base::SplitStringIntoKeyValuePairs(
+ next_link.query(), '=', '&', &parameters)) {
+ for (size_t i = 0; i < parameters.size(); ++i) {
+ if (parameters[i].first == "changestamp") {
+ base::StringToInt64(parameters[i].second, &start_changestamp);
+ } else if (parameters[i].first == "q") {
+ search_query =
+ net::UnescapeURLComponent(parameters[i].second,
+ net::UnescapeRule::URL_SPECIAL_CHARS);
+ } else if (parameters[i].first == "parent") {
+ directory_resource_id =
+ net::UnescapeURLComponent(parameters[i].second,
+ net::UnescapeRule::URL_SPECIAL_CHARS);
+ } else if (parameters[i].first == "start-offset") {
+ base::StringToInt(parameters[i].second, &start_offset);
+ } else if (parameters[i].first == "max-results") {
+ base::StringToInt(parameters[i].second, &max_results);
+ }
+ }
+ }
+
+ GetChangeListInternal(start_changestamp, search_query, directory_resource_id,
+ start_offset, max_results, NULL, callback);
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetRemainingFileList(
+ const GURL& next_link,
+ const FileListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!next_link.is_empty());
+ DCHECK(!callback.is_null());
+
+ return GetRemainingChangeList(
+ next_link, base::Bind(&FileListCallbackAdapter, callback));
+}
+
+CancelCallback FakeDriveService::GetFileResource(
+ const std::string& resource_id,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (entry && entry->change_resource.file()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, base::Passed(make_scoped_ptr(
+ new FileResource(*entry->change_resource.file())))));
+ return CancelCallback();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetShareUrl(
+ const std::string& resource_id,
+ const GURL& /* embed_origin */,
+ const GetShareUrlCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ GURL()));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, entry->share_url));
+ return CancelCallback();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND, GURL()));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetAboutResource(
+ const AboutResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ scoped_ptr<AboutResource> null;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION, base::Passed(&null)));
+ return CancelCallback();
+ }
+
+ ++about_resource_load_count_;
+ scoped_ptr<AboutResource> about_resource(new AboutResource(*about_resource_));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ HTTP_SUCCESS, base::Passed(&about_resource)));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(app_info_value_);
+
+ if (offline_) {
+ scoped_ptr<AppList> null;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ base::Passed(&null)));
+ return CancelCallback();
+ }
+
+ ++app_list_load_count_;
+ scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list)));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::DeleteResource(
+ const std::string& resource_id,
+ const std::string& etag,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+
+ ChangeResource* change = &entry->change_resource;
+ const FileResource* file = change->file();
+ if (change->is_deleted()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+
+ if (!etag.empty() && etag != file->etag()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_PRECONDITION));
+ return CancelCallback();
+ }
+
+ if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_FORBIDDEN));
+ return CancelCallback();
+ }
+
+ change->set_deleted(true);
+ AddNewChangestamp(change);
+ change->set_file(scoped_ptr<FileResource>());
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::TrashResource(
+ const std::string& resource_id,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+
+ ChangeResource* change = &entry->change_resource;
+ FileResource* file = change->mutable_file();
+ if (change->is_deleted() || file->labels().is_trashed()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+
+ if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_FORBIDDEN));
+ return CancelCallback();
+ }
+
+ file->mutable_labels()->set_trashed(true);
+ AddNewChangestamp(change);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const DownloadActionCallback& download_action_callback,
+ const GetContentCallback& get_content_callback,
+ const ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!download_action_callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(download_action_callback,
+ DRIVE_NO_CONNECTION,
+ base::FilePath()));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry || entry->change_resource.file()->IsHostedDocument()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath()));
+ return CancelCallback();
+ }
+
+ const FileResource* file = entry->change_resource.file();
+ const std::string& content_data = entry->content_data;
+ int64 file_size = file->file_size();
+ DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
+
+ if (!get_content_callback.is_null()) {
+ const int64 kBlockSize = 5;
+ for (int64 i = 0; i < file_size; i += kBlockSize) {
+ const int64 size = std::min(kBlockSize, file_size - i);
+ scoped_ptr<std::string> content_for_callback(
+ new std::string(content_data.substr(i, size)));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(get_content_callback, HTTP_SUCCESS,
+ base::Passed(&content_for_callback)));
+ }
+ }
+
+ if (!test_util::WriteStringToFile(local_cache_path, content_data)) {
+ // Failed to write the content.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(download_action_callback,
+ DRIVE_FILE_ERROR, base::FilePath()));
+ return CancelCallback();
+ }
+
+ if (!progress_callback.is_null()) {
+ // See also the comment in ResumeUpload(). For testing that clients
+ // can handle the case progress_callback is called multiple times,
+ // here we invoke the callback twice.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(progress_callback, file_size / 2, file_size));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(progress_callback, file_size, file_size));
+ }
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(download_action_callback,
+ HTTP_SUCCESS,
+ local_cache_path));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::CopyResource(
+ const std::string& resource_id,
+ const std::string& in_parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ const std::string& parent_resource_id = in_parent_resource_id.empty() ?
+ GetRootResourceId() : in_parent_resource_id;
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ // Make a copy and set the new resource ID and the new title.
+ scoped_ptr<EntryInfo> copied_entry(new EntryInfo);
+ copied_entry->content_data = entry->content_data;
+ copied_entry->share_url = entry->share_url;
+ copied_entry->change_resource.set_file(
+ make_scoped_ptr(new FileResource(*entry->change_resource.file())));
+
+ ChangeResource* new_change = &copied_entry->change_resource;
+ FileResource* new_file = new_change->mutable_file();
+ const std::string new_resource_id = GetNewResourceId();
+ new_change->set_file_id(new_resource_id);
+ new_file->set_file_id(new_resource_id);
+ new_file->set_title(new_title);
+
+ ParentReference parent;
+ parent.set_file_id(parent_resource_id);
+ parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
+ std::vector<ParentReference> parents;
+ parents.push_back(parent);
+ *new_file->mutable_parents() = parents;
+
+ if (!last_modified.is_null())
+ new_file->set_modified_date(last_modified);
+
+ AddNewChangestamp(new_change);
+ UpdateETag(new_file);
+
+ // Add the new entry to the map.
+ entries_[new_resource_id] = copied_entry.release();
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ HTTP_SUCCESS,
+ base::Passed(make_scoped_ptr(new FileResource(*new_file)))));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::UpdateResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const base::Time& last_viewed_by_me,
+ const google_apis::drive::Properties& properties,
+ const google_apis::FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ if (!UserHasWriteAccess(entry->user_permission)) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_FORBIDDEN,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ ChangeResource* change = &entry->change_resource;
+ FileResource* file = change->mutable_file();
+
+ if (!new_title.empty())
+ file->set_title(new_title);
+
+ // Set parent if necessary.
+ if (!parent_resource_id.empty()) {
+ ParentReference parent;
+ parent.set_file_id(parent_resource_id);
+ parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
+
+ std::vector<ParentReference> parents;
+ parents.push_back(parent);
+ *file->mutable_parents() = parents;
+ }
+
+ if (!last_modified.is_null())
+ file->set_modified_date(last_modified);
+
+ if (!last_viewed_by_me.is_null())
+ file->set_last_viewed_by_me_date(last_viewed_by_me);
+
+ AddNewChangestamp(change);
+ UpdateETag(file);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS,
+ base::Passed(make_scoped_ptr(new FileResource(*file)))));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::AddResourceToDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+
+ ChangeResource* change = &entry->change_resource;
+ // On the real Drive server, resources do not necessary shape a tree
+ // structure. That is, each resource can have multiple parent.
+ // We mimic the behavior here; AddResourceToDirectoy just adds
+ // one more parent, not overwriting old ones.
+ ParentReference parent;
+ parent.set_file_id(parent_resource_id);
+ parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
+ change->mutable_file()->mutable_parents()->push_back(parent);
+
+ AddNewChangestamp(change);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::RemoveResourceFromDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+
+ ChangeResource* change = &entry->change_resource;
+ FileResource* file = change->mutable_file();
+ std::vector<ParentReference>* parents = file->mutable_parents();
+ for (size_t i = 0; i < parents->size(); ++i) {
+ if ((*parents)[i].file_id() == parent_resource_id) {
+ parents->erase(parents->begin() + i);
+ AddNewChangestamp(change);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+ }
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::AddNewDirectory(
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const FileResourceCallback& callback) {
+ return AddNewDirectoryWithResourceId(
+ "",
+ parent_resource_id.empty() ? GetRootResourceId() : parent_resource_id,
+ directory_title,
+ options,
+ callback);
+}
+
+CancelCallback FakeDriveService::InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const InitiateUploadCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, DRIVE_NO_CONNECTION, GURL()));
+ return CancelCallback();
+ }
+
+ if (parent_resource_id != GetRootResourceId() &&
+ !entries_.count(parent_resource_id)) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND, GURL()));
+ return CancelCallback();
+ }
+
+ GURL session_url = GetNewUploadSessionUrl();
+ upload_sessions_[session_url] =
+ UploadSession(content_type, content_length,
+ parent_resource_id,
+ "", // resource_id
+ "", // etag
+ title);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, session_url));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const InitiateUploadCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, DRIVE_NO_CONNECTION, GURL()));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND, GURL()));
+ return CancelCallback();
+ }
+
+ if (!UserHasWriteAccess(entry->user_permission)) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_FORBIDDEN, GURL()));
+ return CancelCallback();
+ }
+
+ FileResource* file = entry->change_resource.mutable_file();
+ if (!options.etag.empty() && options.etag != file->etag()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_PRECONDITION, GURL()));
+ return CancelCallback();
+ }
+ // TODO(hashimoto): Update |file|'s metadata with |options|.
+
+ GURL session_url = GetNewUploadSessionUrl();
+ upload_sessions_[session_url] =
+ UploadSession(content_type, content_length,
+ "", // parent_resource_id
+ resource_id,
+ file->etag(),
+ "" /* title */);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, session_url));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::GetUploadStatus(
+ const GURL& upload_url,
+ int64 content_length,
+ const UploadRangeCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const UploadRangeCallback& callback,
+ const ProgressCallback& progress_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileResourceCallback completion_callback
+ = base::Bind(&ScheduleUploadRangeCallback,
+ callback, start_position, end_position);
+
+ if (offline_) {
+ completion_callback.Run(DRIVE_NO_CONNECTION, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+
+ if (!upload_sessions_.count(upload_url)) {
+ completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+
+ UploadSession* session = &upload_sessions_[upload_url];
+
+ // Chunks are required to be sent in such a ways that they fill from the start
+ // of the not-yet-uploaded part with no gaps nor overlaps.
+ if (session->uploaded_size != start_position) {
+ completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+
+ if (!progress_callback.is_null()) {
+ // In the real GDataWapi/Drive DriveService, progress is reported in
+ // nondeterministic timing. In this fake implementation, we choose to call
+ // it twice per one ResumeUpload. This is for making sure that client code
+ // works fine even if the callback is invoked more than once; it is the
+ // crucial difference of the progress callback from others.
+ // Note that progress is notified in the relative offset in each chunk.
+ const int64 chunk_size = end_position - start_position;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size));
+ }
+
+ if (content_length != end_position) {
+ session->uploaded_size = end_position;
+ completion_callback.Run(HTTP_RESUME_INCOMPLETE, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+
+ std::string content_data;
+ if (!base::ReadFileToString(local_file_path, &content_data)) {
+ session->uploaded_size = end_position;
+ completion_callback.Run(DRIVE_FILE_ERROR, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+ session->uploaded_size = end_position;
+
+ // |resource_id| is empty if the upload is for new file.
+ if (session->resource_id.empty()) {
+ DCHECK(!session->parent_resource_id.empty());
+ DCHECK(!session->title.empty());
+ const EntryInfo* new_entry = AddNewEntry(
+ "", // auto generate resource id.
+ session->content_type,
+ content_data,
+ session->parent_resource_id,
+ session->title,
+ false); // shared_with_me
+ if (!new_entry) {
+ completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+
+ completion_callback.Run(HTTP_CREATED, make_scoped_ptr(
+ new FileResource(*new_entry->change_resource.file())));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(session->resource_id);
+ if (!entry) {
+ completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+
+ ChangeResource* change = &entry->change_resource;
+ FileResource* file = change->mutable_file();
+ if (file->etag().empty() || session->etag != file->etag()) {
+ completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<FileResource>());
+ return CancelCallback();
+ }
+
+ file->set_md5_checksum(base::MD5String(content_data));
+ entry->content_data = content_data;
+ file->set_file_size(end_position);
+ AddNewChangestamp(change);
+ UpdateETag(file);
+
+ completion_callback.Run(HTTP_SUCCESS, make_scoped_ptr(
+ new FileResource(*file)));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const FileResourceCallback& callback,
+ const ProgressCallback& progress_callback) {
+ CallResumeUpload* const call_resume_upload = new CallResumeUpload();
+ call_resume_upload->service = weak_ptr_factory_.GetWeakPtr();
+ call_resume_upload->content_type = content_type;
+ call_resume_upload->content_length = content_length;
+ call_resume_upload->local_file_path = local_file_path;
+ call_resume_upload->callback = callback;
+ call_resume_upload->progress_callback = progress_callback;
+ InitiateUploadNewFile(
+ content_type,
+ content_length,
+ parent_resource_id,
+ title,
+ options,
+ base::Bind(&CallResumeUpload::Run, base::Owned(call_resume_upload)));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const FileResourceCallback& callback,
+ const ProgressCallback& progress_callback) {
+ CallResumeUpload* const call_resume_upload = new CallResumeUpload();
+ call_resume_upload->service = weak_ptr_factory_.GetWeakPtr();
+ call_resume_upload->content_type = content_type;
+ call_resume_upload->content_length = content_length;
+ call_resume_upload->local_file_path = local_file_path;
+ call_resume_upload->callback = callback;
+ call_resume_upload->progress_callback = progress_callback;
+ InitiateUploadExistingFile(
+ content_type,
+ content_length,
+ resource_id,
+ options,
+ base::Bind(&CallResumeUpload::Run, base::Owned(call_resume_upload)));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::AuthorizeApp(
+ const std::string& resource_id,
+ const std::string& app_id,
+ const AuthorizeAppCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (entries_.count(resource_id) == 0) {
+ callback.Run(google_apis::HTTP_NOT_FOUND, GURL());
+ return CancelCallback();
+ }
+
+ callback.Run(HTTP_SUCCESS,
+ GURL(base::StringPrintf(open_url_format_.c_str(),
+ resource_id.c_str(),
+ app_id.c_str())));
+ return CancelCallback();
+}
+
+CancelCallback FakeDriveService::UninstallApp(
+ const std::string& app_id,
+ const google_apis::EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, google_apis::DRIVE_NO_CONNECTION));
+ return CancelCallback();
+ }
+
+ // Find app_id from app_info_value_ and delete.
+ base::ListValue* items = NULL;
+ if (!app_info_value_->GetList("items", &items)) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, google_apis::HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+
+ for (size_t i = 0; i < items->GetSize(); ++i) {
+ base::DictionaryValue* item = NULL;
+ std::string id;
+ if (items->GetDictionary(i, &item) && item->GetString("id", &id) &&
+ id == app_id) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ items->Remove(i, NULL) ? google_apis::HTTP_NO_CONTENT
+ : google_apis::HTTP_NOT_FOUND));
+ return CancelCallback();
+ }
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, google_apis::HTTP_NOT_FOUND));
+ return CancelCallback();
+}
+
+void FakeDriveService::AddNewFile(const std::string& content_type,
+ const std::string& content_data,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ bool shared_with_me,
+ const FileResourceCallback& callback) {
+ AddNewFileWithResourceId("", content_type, content_data, parent_resource_id,
+ title, shared_with_me, callback);
+}
+
+void FakeDriveService::AddNewFileWithResourceId(
+ const std::string& resource_id,
+ const std::string& content_type,
+ const std::string& content_data,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ bool shared_with_me,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ base::Passed(scoped_ptr<FileResource>())));
+ return;
+ }
+
+ const EntryInfo* new_entry = AddNewEntry(resource_id,
+ content_type,
+ content_data,
+ parent_resource_id,
+ title,
+ shared_with_me);
+ if (!new_entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND,
+ base::Passed(scoped_ptr<FileResource>())));
+ return;
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_CREATED,
+ base::Passed(make_scoped_ptr(
+ new FileResource(*new_entry->change_resource.file())))));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+CancelCallback FakeDriveService::AddNewDirectoryWithResourceId(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ const EntryInfo* new_entry = AddNewEntry(resource_id,
+ util::kDriveFolderMimeType,
+ "", // content_data
+ parent_resource_id,
+ directory_title,
+ false); // shared_with_me
+ if (!new_entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND,
+ base::Passed(scoped_ptr<FileResource>())));
+ return CancelCallback();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_CREATED,
+ base::Passed(make_scoped_ptr(
+ new FileResource(*new_entry->change_resource.file())))));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeDriveService::NotifyObservers,
+ weak_ptr_factory_.GetWeakPtr()));
+ return CancelCallback();
+}
+
+void FakeDriveService::SetLastModifiedTime(
+ const std::string& resource_id,
+ const base::Time& last_modified_time,
+ const FileResourceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ base::Passed(scoped_ptr<FileResource>())));
+ return;
+ }
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_NOT_FOUND,
+ base::Passed(scoped_ptr<FileResource>())));
+ return;
+ }
+
+ ChangeResource* change = &entry->change_resource;
+ FileResource* file = change->mutable_file();
+ file->set_modified_date(last_modified_time);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS,
+ base::Passed(make_scoped_ptr(new FileResource(*file)))));
+}
+
+google_apis::DriveApiErrorCode FakeDriveService::SetUserPermission(
+ const std::string& resource_id,
+ google_apis::drive::PermissionRole user_permission) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ EntryInfo* entry = FindEntryByResourceId(resource_id);
+ if (!entry)
+ return HTTP_NOT_FOUND;
+
+ entry->user_permission = user_permission;
+ return HTTP_SUCCESS;
+}
+
+void FakeDriveService::AddChangeObserver(ChangeObserver* change_observer) {
+ change_observers_.AddObserver(change_observer);
+}
+
+void FakeDriveService::RemoveChangeObserver(ChangeObserver* change_observer) {
+ change_observers_.RemoveObserver(change_observer);
+}
+
+FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId(
+ const std::string& resource_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ EntryInfoMap::iterator it = entries_.find(resource_id);
+ // Deleted entries don't have FileResource.
+ return it != entries_.end() && it->second->change_resource.file() ?
+ it->second : NULL;
+}
+
+std::string FakeDriveService::GetNewResourceId() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ ++resource_id_count_;
+ return base::StringPrintf("resource_id_%d", resource_id_count_);
+}
+
+void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
+ file->set_etag(
+ "etag_" + base::Int64ToString(about_resource_->largest_change_id()));
+}
+
+void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change) {
+ about_resource_->set_largest_change_id(
+ about_resource_->largest_change_id() + 1);
+ change->set_change_id(about_resource_->largest_change_id());
+}
+
+const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry(
+ const std::string& given_resource_id,
+ const std::string& content_type,
+ const std::string& content_data,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ bool shared_with_me) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!parent_resource_id.empty() &&
+ parent_resource_id != GetRootResourceId() &&
+ !entries_.count(parent_resource_id)) {
+ return NULL;
+ }
+
+ const std::string resource_id =
+ given_resource_id.empty() ? GetNewResourceId() : given_resource_id;
+ if (entries_.count(resource_id))
+ return NULL;
+ GURL upload_url = GURL("https://xxx/upload/" + resource_id);
+
+ scoped_ptr<EntryInfo> new_entry(new EntryInfo);
+ ChangeResource* new_change = &new_entry->change_resource;
+ FileResource* new_file = new FileResource;
+ new_change->set_file(make_scoped_ptr(new_file));
+
+ // Set the resource ID and the title
+ new_change->set_file_id(resource_id);
+ new_file->set_file_id(resource_id);
+ new_file->set_title(title);
+ // Set the contents, size and MD5 for a file.
+ if (content_type != util::kDriveFolderMimeType &&
+ !util::IsKnownHostedDocumentMimeType(content_type)) {
+ new_entry->content_data = content_data;
+ new_file->set_file_size(content_data.size());
+ new_file->set_md5_checksum(base::MD5String(content_data));
+ }
+
+ if (shared_with_me) {
+ // Set current time to mark the file as shared_with_me.
+ new_file->set_shared_with_me_date(base::Time::Now());
+ }
+
+ std::string escaped_resource_id = net::EscapePath(resource_id);
+
+ // Set mime type.
+ new_file->set_mime_type(content_type);
+
+ // Set alternate link if needed.
+ if (content_type == util::kGoogleDocumentMimeType)
+ new_file->set_alternate_link(GURL("https://document_alternate_link"));
+
+ // Set parents.
+ if (!parent_resource_id.empty()) {
+ ParentReference parent;
+ parent.set_file_id(parent_resource_id);
+ parent.set_parent_link(GetFakeLinkUrl(parent.file_id()));
+ std::vector<ParentReference> parents;
+ parents.push_back(parent);
+ *new_file->mutable_parents() = parents;
+ }
+
+ new_entry->share_url = net::AppendOrReplaceQueryParameter(
+ share_url_base_, "name", title);
+
+ AddNewChangestamp(new_change);
+ UpdateETag(new_file);
+
+ base::Time published_date =
+ base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_);
+ new_file->set_created_date(published_date);
+
+ EntryInfo* raw_new_entry = new_entry.release();
+ entries_[resource_id] = raw_new_entry;
+ return raw_new_entry;
+}
+
+void FakeDriveService::GetChangeListInternal(
+ int64 start_changestamp,
+ const std::string& search_query,
+ const std::string& directory_resource_id,
+ int start_offset,
+ int max_results,
+ int* load_counter,
+ const ChangeListCallback& callback) {
+ if (offline_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ DRIVE_NO_CONNECTION,
+ base::Passed(scoped_ptr<ChangeList>())));
+ return;
+ }
+
+ // Filter out entries per parameters like |directory_resource_id| and
+ // |search_query|.
+ ScopedVector<ChangeResource> entries;
+ int num_entries_matched = 0;
+ for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end();
+ ++it) {
+ const ChangeResource& entry = it->second->change_resource;
+ bool should_exclude = false;
+
+ // If |directory_resource_id| is set, exclude the entry if it's not in
+ // the target directory.
+ if (!directory_resource_id.empty()) {
+ // Get the parent resource ID of the entry.
+ std::string parent_resource_id;
+ if (entry.file() && !entry.file()->parents().empty())
+ parent_resource_id = entry.file()->parents()[0].file_id();
+
+ if (directory_resource_id != parent_resource_id)
+ should_exclude = true;
+ }
+
+ // If |search_query| is set, exclude the entry if it does not contain the
+ // search query in the title.
+ if (!should_exclude && !search_query.empty() &&
+ !EntryMatchWithQuery(entry, search_query)) {
+ should_exclude = true;
+ }
+
+ // If |start_changestamp| is set, exclude the entry if the
+ // changestamp is older than |largest_changestamp|.
+ // See https://developers.google.com/google-apps/documents-list/
+ // #retrieving_all_changes_since_a_given_changestamp
+ if (start_changestamp > 0 && entry.change_id() < start_changestamp)
+ should_exclude = true;
+
+ // If the caller requests other list than change list by specifying
+ // zero-|start_changestamp|, exclude deleted entry from the result.
+ const bool deleted = entry.is_deleted() ||
+ (entry.file() && entry.file()->labels().is_trashed());
+ if (!start_changestamp && deleted)
+ should_exclude = true;
+
+ // The entry matched the criteria for inclusion.
+ if (!should_exclude)
+ ++num_entries_matched;
+
+ // If |start_offset| is set, exclude the entry if the entry is before the
+ // start index. <= instead of < as |num_entries_matched| was
+ // already incremented.
+ if (start_offset > 0 && num_entries_matched <= start_offset)
+ should_exclude = true;
+
+ if (!should_exclude) {
+ scoped_ptr<ChangeResource> entry_copied(new ChangeResource);
+ entry_copied->set_change_id(entry.change_id());
+ entry_copied->set_file_id(entry.file_id());
+ entry_copied->set_deleted(entry.is_deleted());
+ if (entry.file()) {
+ entry_copied->set_file(
+ make_scoped_ptr(new FileResource(*entry.file())));
+ }
+ entry_copied->set_modification_date(entry.modification_date());
+ entries.push_back(entry_copied.release());
+ }
+ }
+
+ scoped_ptr<ChangeList> change_list(new ChangeList);
+ if (start_changestamp > 0 && start_offset == 0) {
+ change_list->set_largest_change_id(about_resource_->largest_change_id());
+ }
+
+ // If |max_results| is set, trim the entries if the number exceeded the max
+ // results.
+ if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) {
+ entries.erase(entries.begin() + max_results, entries.end());
+ // Adds the next URL.
+ // Here, we embed information which is needed for continuing the
+ // GetChangeList request in the next invocation into url query
+ // parameters.
+ GURL next_url(base::StringPrintf(
+ "http://localhost/?start-offset=%d&max-results=%d",
+ start_offset + max_results,
+ max_results));
+ if (start_changestamp > 0) {
+ next_url = net::AppendOrReplaceQueryParameter(
+ next_url, "changestamp",
+ base::Int64ToString(start_changestamp).c_str());
+ }
+ if (!search_query.empty()) {
+ next_url = net::AppendOrReplaceQueryParameter(
+ next_url, "q", search_query);
+ }
+ if (!directory_resource_id.empty()) {
+ next_url = net::AppendOrReplaceQueryParameter(
+ next_url, "parent", directory_resource_id);
+ }
+
+ change_list->set_next_link(next_url);
+ }
+ *change_list->mutable_items() = entries.Pass();
+
+ if (load_counter)
+ *load_counter += 1;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, HTTP_SUCCESS, base::Passed(&change_list)));
+}
+
+GURL FakeDriveService::GetNewUploadSessionUrl() {
+ return GURL("https://upload_session_url/" +
+ base::Int64ToString(next_upload_sequence_number_++));
+}
+
+google_apis::CancelCallback FakeDriveService::AddPermission(
+ const std::string& resource_id,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const google_apis::EntryActionCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ NOTREACHED();
+ return CancelCallback();
+}
+
+scoped_ptr<BatchRequestConfiguratorInterface>
+FakeDriveService::StartBatchRequest() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ NOTREACHED();
+ return scoped_ptr<BatchRequestConfiguratorInterface>();
+}
+
+void FakeDriveService::NotifyObservers() {
+ FOR_EACH_OBSERVER(ChangeObserver, change_observers_, OnNewChangeAvailable());
+}
+
+} // namespace drive
diff --git a/components/drive/service/fake_drive_service.h b/components/drive/service/fake_drive_service.h
new file mode 100644
index 0000000..09ae14a
--- /dev/null
+++ b/components/drive/service/fake_drive_service.h
@@ -0,0 +1,406 @@
+// 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.
+
+#ifndef COMPONENTS_DRIVE_SERVICE_FAKE_DRIVE_SERVICE_H_
+#define COMPONENTS_DRIVE_SERVICE_FAKE_DRIVE_SERVICE_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/service/drive_service_interface.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace google_apis {
+class AboutResource;
+class ChangeResource;
+class FileResource;
+}
+
+namespace drive {
+
+// This class implements a fake DriveService which acts like a real Drive
+// service. The fake service works as follows:
+//
+// 1) Load JSON files and construct the in-memory resource list.
+// 2) Return valid responses based on the the in-memory resource list.
+// 3) Update the in-memory resource list by requests like DeleteResource().
+class FakeDriveService : public DriveServiceInterface {
+ public:
+ class ChangeObserver {
+ public:
+ virtual ~ChangeObserver() {}
+ virtual void OnNewChangeAvailable() = 0;
+ };
+
+ FakeDriveService();
+ ~FakeDriveService() override;
+
+ // Loads the app list for Drive API. Returns true on success.
+ bool LoadAppListForDriveApi(const std::string& relative_path);
+
+ // Adds an app to app list.
+ void AddApp(const std::string& app_id,
+ const std::string& app_name,
+ const std::string& product_id,
+ const std::string& create_url,
+ bool is_removable);
+
+ // Removes an app by product id.
+ void RemoveAppByProductId(const std::string& product_id);
+
+ // Returns true if the service knows the given drive app id.
+ bool HasApp(const std::string& app_id) const;
+
+ // Changes the offline state. All functions fail with DRIVE_NO_CONNECTION
+ // when offline. By default the offline state is false.
+ void set_offline(bool offline) { offline_ = offline; }
+
+ // GetAllFileList never returns result when this is set to true.
+ // Used to emulate the real server's slowness.
+ void set_never_return_all_file_list(bool value) {
+ never_return_all_file_list_ = value;
+ }
+
+ // Changes the default max results returned from GetAllFileList().
+ // By default, it's set to 0, which is unlimited.
+ void set_default_max_results(int default_max_results) {
+ default_max_results_ = default_max_results;
+ }
+
+ // Sets the url to the test server to be used as a base for generated share
+ // urls to the share dialog.
+ void set_share_url_base(const GURL& share_url_base) {
+ share_url_base_ = share_url_base;
+ }
+
+ // Changes the quota fields returned from GetAboutResource().
+ void SetQuotaValue(int64 used, int64 total);
+
+ // Returns the AboutResource.
+ const google_apis::AboutResource& about_resource() const {
+ return *about_resource_;
+ }
+
+ // Returns the number of times the file list is successfully loaded by
+ // GetAllFileList().
+ int file_list_load_count() const { return file_list_load_count_; }
+
+ // Returns the number of times the resource list is successfully loaded by
+ // GetChangeList().
+ int change_list_load_count() const { return change_list_load_count_; }
+
+ // Returns the number of times the resource list is successfully loaded by
+ // GetFileListInDirectory().
+ int directory_load_count() const { return directory_load_count_; }
+
+ // Returns the number of times the about resource is successfully loaded
+ // by GetAboutResource().
+ int about_resource_load_count() const {
+ return about_resource_load_count_;
+ }
+
+ // Returns the number of times the app list is successfully loaded by
+ // GetAppList().
+ int app_list_load_count() const { return app_list_load_count_; }
+
+ // Returns the number of times GetAllFileList are blocked due to
+ // set_never_return_all_file_list().
+ int blocked_file_list_load_count() const {
+ return blocked_file_list_load_count_;
+ }
+
+ // Returns the file path whose request is cancelled just before this method
+ // invocation.
+ const base::FilePath& last_cancelled_file() const {
+ return last_cancelled_file_;
+ }
+
+ // Returns the (fake) URL for the link.
+ static GURL GetFakeLinkUrl(const std::string& resource_id);
+
+ // Sets the printf format for constructing the response of AuthorizeApp().
+ // The format string must include two %s that are to be filled with
+ // resource_id and app_id.
+ void set_open_url_format(const std::string& url_format) {
+ open_url_format_ = url_format;
+ }
+
+ // DriveServiceInterface Overrides
+ void Initialize(const std::string& account_id) override;
+ void AddObserver(DriveServiceObserver* observer) override;
+ void RemoveObserver(DriveServiceObserver* observer) override;
+ bool CanSendRequest() const override;
+ std::string GetRootResourceId() const override;
+ bool HasAccessToken() const override;
+ void RequestAccessToken(
+ const google_apis::AuthStatusCallback& callback) override;
+ bool HasRefreshToken() const override;
+ void ClearAccessToken() override;
+ void ClearRefreshToken() override;
+ google_apis::CancelCallback GetAllFileList(
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetFileListInDirectory(
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) override;
+ // See the comment for EntryMatchWidthQuery() in .cc file for details about
+ // the supported search query types.
+ google_apis::CancelCallback Search(
+ const std::string& search_query,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback SearchByTitle(
+ const std::string& title,
+ const std::string& directory_resource_id,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetChangeList(
+ int64 start_changestamp,
+ const google_apis::ChangeListCallback& callback) override;
+ google_apis::CancelCallback GetRemainingChangeList(
+ const GURL& next_link,
+ const google_apis::ChangeListCallback& callback) override;
+ google_apis::CancelCallback GetRemainingFileList(
+ const GURL& next_link,
+ const google_apis::FileListCallback& callback) override;
+ google_apis::CancelCallback GetFileResource(
+ const std::string& resource_id,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback GetShareUrl(
+ const std::string& resource_id,
+ const GURL& embed_origin,
+ const google_apis::GetShareUrlCallback& callback) override;
+ google_apis::CancelCallback GetAboutResource(
+ const google_apis::AboutResourceCallback& callback) override;
+ google_apis::CancelCallback GetAppList(
+ const google_apis::AppListCallback& callback) override;
+ google_apis::CancelCallback DeleteResource(
+ const std::string& resource_id,
+ const std::string& etag,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback TrashResource(
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const google_apis::DownloadActionCallback& download_action_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback CopyResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback UpdateResource(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const base::Time& last_viewed_by_me,
+ const google_apis::drive::Properties& properties,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback AddResourceToDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback RemoveResourceFromDirectory(
+ const std::string& parent_resource_id,
+ const std::string& resource_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback AddNewDirectory(
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const google_apis::FileResourceCallback& callback) override;
+ google_apis::CancelCallback InitiateUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const UploadNewFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) override;
+ google_apis::CancelCallback InitiateUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const UploadExistingFileOptions& options,
+ const google_apis::InitiateUploadCallback& callback) override;
+ google_apis::CancelCallback ResumeUpload(
+ const GURL& upload_url,
+ int64 start_position,
+ int64 end_position,
+ int64 content_length,
+ const std::string& content_type,
+ const base::FilePath& local_file_path,
+ const google_apis::drive::UploadRangeCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback GetUploadStatus(
+ const GURL& upload_url,
+ int64 content_length,
+ const google_apis::drive::UploadRangeCallback& callback) override;
+ google_apis::CancelCallback MultipartUploadNewFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ const base::FilePath& local_file_path,
+ const UploadNewFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback MultipartUploadExistingFile(
+ const std::string& content_type,
+ int64 content_length,
+ const std::string& resource_id,
+ const base::FilePath& local_file_path,
+ const UploadExistingFileOptions& options,
+ const google_apis::FileResourceCallback& callback,
+ const google_apis::ProgressCallback& progress_callback) override;
+ google_apis::CancelCallback AuthorizeApp(
+ const std::string& resource_id,
+ const std::string& app_id,
+ const google_apis::AuthorizeAppCallback& callback) override;
+ google_apis::CancelCallback UninstallApp(
+ const std::string& app_id,
+ const google_apis::EntryActionCallback& callback) override;
+ google_apis::CancelCallback AddPermission(
+ const std::string& resource_id,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const google_apis::EntryActionCallback& callback) override;
+ scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() override;
+
+ // Adds a new file with the given parameters. On success, returns
+ // HTTP_CREATED with the parsed entry.
+ // |callback| must not be null.
+ void AddNewFile(const std::string& content_type,
+ const std::string& content_data,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ bool shared_with_me,
+ const google_apis::FileResourceCallback& callback);
+
+ // Adds a new file with the given |resource_id|. If the id already exists,
+ // it's an error. This is used for testing cross profile file sharing that
+ // needs to have matching resource IDs in different fake service instances.
+ // |callback| must not be null.
+ void AddNewFileWithResourceId(
+ const std::string& resource_id,
+ const std::string& content_type,
+ const std::string& content_data,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ bool shared_with_me,
+ const google_apis::FileResourceCallback& callback);
+
+ // Adds a new directory with the given |resource_id|.
+ // |callback| must not be null.
+ google_apis::CancelCallback AddNewDirectoryWithResourceId(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& directory_title,
+ const AddNewDirectoryOptions& options,
+ const google_apis::FileResourceCallback& callback);
+
+ // Sets the last modified time for an entry specified by |resource_id|.
+ // On success, returns HTTP_SUCCESS with the parsed entry.
+ // |callback| must not be null.
+ void SetLastModifiedTime(
+ const std::string& resource_id,
+ const base::Time& last_modified_time,
+ const google_apis::FileResourceCallback& callback);
+
+ // Sets the user's permission for an entry specified by |resource_id|.
+ google_apis::DriveApiErrorCode SetUserPermission(
+ const std::string& resource_id,
+ google_apis::drive::PermissionRole user_permission);
+
+ void AddChangeObserver(ChangeObserver* observer);
+ void RemoveChangeObserver(ChangeObserver* observer);
+
+ private:
+ struct EntryInfo;
+ struct UploadSession;
+
+ // Returns a pointer to the entry that matches |resource_id|, or NULL if
+ // not found.
+ EntryInfo* FindEntryByResourceId(const std::string& resource_id);
+
+ // Returns a new resource ID, which looks like "resource_id_<num>" where
+ // <num> is a monotonically increasing number starting from 1.
+ std::string GetNewResourceId();
+
+ // Increments |largest_changestamp_| and adds the new changestamp.
+ void AddNewChangestamp(google_apis::ChangeResource* change);
+
+ // Update ETag of |file| based on |largest_changestamp_|.
+ void UpdateETag(google_apis::FileResource* file);
+
+ // Adds a new entry based on the given parameters.
+ // |resource_id| can be empty, in the case, the id is automatically generated.
+ // Returns a pointer to the newly added entry, or NULL if failed.
+ const EntryInfo* AddNewEntry(
+ const std::string& resource_id,
+ const std::string& content_type,
+ const std::string& content_data,
+ const std::string& parent_resource_id,
+ const std::string& title,
+ bool shared_with_me);
+
+ // Core implementation of GetChangeList.
+ // This method returns the slice of the all matched entries, and its range
+ // is between |start_offset| (inclusive) and |start_offset| + |max_results|
+ // (exclusive).
+ // Increments *load_counter by 1 before it returns successfully.
+ void GetChangeListInternal(
+ int64 start_changestamp,
+ const std::string& search_query,
+ const std::string& directory_resource_id,
+ int start_offset,
+ int max_results,
+ int* load_counter,
+ const google_apis::ChangeListCallback& callback);
+
+ // Returns new upload session URL.
+ GURL GetNewUploadSessionUrl();
+
+ void NotifyObservers();
+
+ // The class is expected to run on UI thread.
+ base::ThreadChecker thread_checker_;
+
+ typedef std::map<std::string, EntryInfo*> EntryInfoMap;
+ EntryInfoMap entries_;
+ scoped_ptr<google_apis::AboutResource> about_resource_;
+ scoped_ptr<base::DictionaryValue> app_info_value_;
+ std::map<GURL, UploadSession> upload_sessions_;
+ int64 published_date_seq_;
+ int64 next_upload_sequence_number_;
+ int default_max_results_;
+ int resource_id_count_;
+ int file_list_load_count_;
+ int change_list_load_count_;
+ int directory_load_count_;
+ int about_resource_load_count_;
+ int app_list_load_count_;
+ int blocked_file_list_load_count_;
+ bool offline_;
+ bool never_return_all_file_list_;
+ base::FilePath last_cancelled_file_;
+ GURL share_url_base_;
+ std::string app_json_template_;
+ std::string open_url_format_;
+
+ base::ObserverList<ChangeObserver> change_observers_;
+
+ base::WeakPtrFactory<FakeDriveService> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDriveService);
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SERVICE_FAKE_DRIVE_SERVICE_H_
diff --git a/components/drive/service/fake_drive_service_unittest.cc b/components/drive/service/fake_drive_service_unittest.cc
new file mode 100644
index 0000000..009f8ca
--- /dev/null
+++ b/components/drive/service/fake_drive_service_unittest.cc
@@ -0,0 +1,2177 @@
+// 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 "components/drive/service/fake_drive_service.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/md5.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/drive/service/test_util.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using google_apis::AboutResource;
+using google_apis::AppList;
+using google_apis::ChangeList;
+using google_apis::ChangeResource;
+using google_apis::FileList;
+using google_apis::FileResource;
+using google_apis::DRIVE_NO_CONNECTION;
+using google_apis::DRIVE_OTHER_ERROR;
+using google_apis::DriveApiErrorCode;
+using google_apis::GetContentCallback;
+using google_apis::HTTP_CREATED;
+using google_apis::HTTP_FORBIDDEN;
+using google_apis::HTTP_NOT_FOUND;
+using google_apis::HTTP_NO_CONTENT;
+using google_apis::HTTP_PRECONDITION;
+using google_apis::HTTP_RESUME_INCOMPLETE;
+using google_apis::HTTP_SUCCESS;
+using google_apis::ProgressCallback;
+using google_apis::UploadRangeResponse;
+
+namespace drive {
+
+namespace test_util {
+
+using google_apis::test_util::AppendProgressCallbackResult;
+using google_apis::test_util::CreateCopyResultCallback;
+using google_apis::test_util::ProgressInfo;
+using google_apis::test_util::TestGetContentCallback;
+using google_apis::test_util::WriteStringToFile;
+
+} // namespace test_util
+
+namespace {
+
+class FakeDriveServiceTest : public testing::Test {
+ protected:
+ // Returns the resource entry that matches |resource_id|.
+ scoped_ptr<FileResource> FindEntry(const std::string& resource_id) {
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.GetFileResource(
+ resource_id, test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ return entry.Pass();
+ }
+
+ // Returns true if the resource identified by |resource_id| exists.
+ bool Exists(const std::string& resource_id) {
+ scoped_ptr<FileResource> entry = FindEntry(resource_id);
+ return entry && !entry->labels().is_trashed();
+ }
+
+ // Adds a new directory at |parent_resource_id| with the given name.
+ // Returns true on success.
+ bool AddNewDirectory(const std::string& parent_resource_id,
+ const std::string& directory_title) {
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewDirectory(
+ parent_resource_id, directory_title, AddNewDirectoryOptions(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ return error == HTTP_CREATED;
+ }
+
+ // Returns true if the resource identified by |resource_id| has a parent
+ // identified by |parent_id|.
+ bool HasParent(const std::string& resource_id, const std::string& parent_id) {
+ scoped_ptr<FileResource> entry = FindEntry(resource_id);
+ if (entry) {
+ for (size_t i = 0; i < entry->parents().size(); ++i) {
+ if (entry->parents()[i].file_id() == parent_id)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ int64 GetLargestChangeByAboutResource() {
+ DriveApiErrorCode error;
+ scoped_ptr<AboutResource> about_resource;
+ fake_service_.GetAboutResource(
+ test_util::CreateCopyResultCallback(&error, &about_resource));
+ base::RunLoop().RunUntilIdle();
+ return about_resource->largest_change_id();
+ }
+
+ base::MessageLoop message_loop_;
+ FakeDriveService fake_service_;
+};
+
+TEST_F(FakeDriveServiceTest, GetAllFileList) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.GetAllFileList(
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check.
+ EXPECT_EQ(15U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.file_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetAllFileList_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.GetAllFileList(
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(file_list);
+}
+
+TEST_F(FakeDriveServiceTest, GetFileListInDirectory_InRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.GetFileListInDirectory(
+ fake_service_.GetRootResourceId(),
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There are 8 entries in the root directory.
+ EXPECT_EQ(8U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.directory_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetFileListInDirectory_InNonRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.GetFileListInDirectory(
+ "1_folder_resource_id",
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There is three entries in 1_folder_resource_id
+ // directory.
+ EXPECT_EQ(3U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.directory_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetFileListInDirectory_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.GetFileListInDirectory(
+ fake_service_.GetRootResourceId(),
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(file_list);
+}
+
+TEST_F(FakeDriveServiceTest, Search) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.Search(
+ "File", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There are 4 entries that contain "File" in their
+ // titles.
+ EXPECT_EQ(4U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, Search_WithAttribute) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.Search(
+ "title:1.txt", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There are 4 entries that contain "1.txt" in their
+ // titles.
+ EXPECT_EQ(4U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, Search_MultipleQueries) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.Search(
+ "Directory 1", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // There are 2 entries that contain both "Directory" and "1" in their titles.
+ EXPECT_EQ(2U, file_list->items().size());
+
+ fake_service_.Search(
+ "\"Directory 1\"", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // There is 1 entry that contain "Directory 1" in its title.
+ EXPECT_EQ(1U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, Search_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.Search(
+ "Directory 1", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(file_list);
+}
+
+TEST_F(FakeDriveServiceTest, Search_Deleted) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ std::string(), // etag
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_NO_CONTENT, error);
+
+ error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.Search(
+ "File", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There are 4 entries that contain "File" in their
+ // titles and one of them is deleted.
+ EXPECT_EQ(3U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, Search_Trashed) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.TrashResource("2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_SUCCESS, error);
+
+ error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.Search(
+ "File", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There are 4 entries that contain "File" in their
+ // titles and one of them is deleted.
+ EXPECT_EQ(3U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, SearchByTitle) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.SearchByTitle(
+ "1.txt", // title
+ fake_service_.GetRootResourceId(), // directory_resource_id
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There are 2 entries that contain "1.txt" in their
+ // titles directly under the root directory.
+ EXPECT_EQ(2U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, SearchByTitle_EmptyDirectoryResourceId) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.SearchByTitle(
+ "1.txt", // title
+ "", // directory resource id
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+ // Do some sanity check. There are 4 entries that contain "1.txt" in their
+ // titles.
+ EXPECT_EQ(4U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, SearchByTitle_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.SearchByTitle(
+ "Directory 1", // title
+ fake_service_.GetRootResourceId(), // directory_resource_id
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(file_list);
+}
+
+TEST_F(FakeDriveServiceTest, GetChangeList_NoNewEntries) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<ChangeList> change_list;
+ fake_service_.GetChangeList(
+ fake_service_.about_resource().largest_change_id() + 1,
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(change_list);
+ EXPECT_EQ(fake_service_.about_resource().largest_change_id(),
+ change_list->largest_change_id());
+ // This should be empty as the latest changestamp was passed to
+ // GetChangeList(), hence there should be no new entries.
+ EXPECT_EQ(0U, change_list->items().size());
+ // It's considered loaded even if the result is empty.
+ EXPECT_EQ(1, fake_service_.change_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetChangeList_WithNewEntry) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ const int64 old_largest_change_id =
+ fake_service_.about_resource().largest_change_id();
+
+ // Add a new directory in the root directory.
+ ASSERT_TRUE(AddNewDirectory(
+ fake_service_.GetRootResourceId(), "new directory"));
+
+ // Get the resource list newer than old_largest_change_id.
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<ChangeList> change_list;
+ fake_service_.GetChangeList(
+ old_largest_change_id + 1,
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(change_list);
+ EXPECT_EQ(fake_service_.about_resource().largest_change_id(),
+ change_list->largest_change_id());
+ // The result should only contain the newly created directory.
+ ASSERT_EQ(1U, change_list->items().size());
+ ASSERT_TRUE(change_list->items()[0]->file());
+ EXPECT_EQ("new directory", change_list->items()[0]->file()->title());
+ EXPECT_EQ(1, fake_service_.change_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetChangeList_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<ChangeList> change_list;
+ fake_service_.GetChangeList(
+ 654321, // start_changestamp
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(change_list);
+}
+
+TEST_F(FakeDriveServiceTest, GetChangeList_DeletedEntry) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ ASSERT_TRUE(Exists("2_file_resource_id"));
+ const int64 old_largest_change_id =
+ fake_service_.about_resource().largest_change_id();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ std::string(), // etag
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(HTTP_NO_CONTENT, error);
+ ASSERT_FALSE(Exists("2_file_resource_id"));
+
+ // Get the resource list newer than old_largest_change_id.
+ error = DRIVE_OTHER_ERROR;
+ scoped_ptr<ChangeList> change_list;
+ fake_service_.GetChangeList(
+ old_largest_change_id + 1,
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(change_list);
+ EXPECT_EQ(fake_service_.about_resource().largest_change_id(),
+ change_list->largest_change_id());
+ // The result should only contain the deleted file.
+ ASSERT_EQ(1U, change_list->items().size());
+ const ChangeResource& item = *change_list->items()[0];
+ EXPECT_EQ("2_file_resource_id", item.file_id());
+ EXPECT_FALSE(item.file());
+ EXPECT_TRUE(item.is_deleted());
+ EXPECT_EQ(1, fake_service_.change_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetChangeList_TrashedEntry) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ ASSERT_TRUE(Exists("2_file_resource_id"));
+ const int64 old_largest_change_id =
+ fake_service_.about_resource().largest_change_id();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.TrashResource("2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(HTTP_SUCCESS, error);
+ ASSERT_FALSE(Exists("2_file_resource_id"));
+
+ // Get the resource list newer than old_largest_change_id.
+ error = DRIVE_OTHER_ERROR;
+ scoped_ptr<ChangeList> change_list;
+ fake_service_.GetChangeList(
+ old_largest_change_id + 1,
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(change_list);
+ EXPECT_EQ(fake_service_.about_resource().largest_change_id(),
+ change_list->largest_change_id());
+ // The result should only contain the trashed file.
+ ASSERT_EQ(1U, change_list->items().size());
+ const ChangeResource& item = *change_list->items()[0];
+ EXPECT_EQ("2_file_resource_id", item.file_id());
+ ASSERT_TRUE(item.file());
+ EXPECT_TRUE(item.file()->labels().is_trashed());
+ EXPECT_EQ(1, fake_service_.change_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetRemainingFileList_GetAllFileList) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_default_max_results(6);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.GetAllFileList(
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ // Do some sanity check.
+ // The number of results is 14 entries. Thus, it should split into three
+ // chunks: 6, 6, and then 2.
+ EXPECT_EQ(6U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.file_list_load_count());
+
+ // Second page loading.
+ // Keep the next url before releasing the |file_list|.
+ GURL next_url(file_list->next_link());
+
+ error = DRIVE_OTHER_ERROR;
+ file_list.reset();
+ fake_service_.GetRemainingFileList(
+ next_url,
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ EXPECT_EQ(6U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.file_list_load_count());
+
+ // Third page loading.
+ next_url = file_list->next_link();
+
+ error = DRIVE_OTHER_ERROR;
+ file_list.reset();
+ fake_service_.GetRemainingFileList(
+ next_url,
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ EXPECT_EQ(3U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.file_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetRemainingFileList_GetFileListInDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_default_max_results(3);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.GetFileListInDirectory(
+ fake_service_.GetRootResourceId(),
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ // Do some sanity check.
+ // The number of results is 8 entries. Thus, it should split into three
+ // chunks: 3, 3, and then 2.
+ EXPECT_EQ(3U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.directory_load_count());
+
+ // Second page loading.
+ // Keep the next url before releasing the |file_list|.
+ GURL next_url = file_list->next_link();
+
+ error = DRIVE_OTHER_ERROR;
+ file_list.reset();
+ fake_service_.GetRemainingFileList(
+ next_url,
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ EXPECT_EQ(3U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.directory_load_count());
+
+ // Third page loading.
+ next_url = file_list->next_link();
+
+ error = DRIVE_OTHER_ERROR;
+ file_list.reset();
+ fake_service_.GetRemainingFileList(
+ next_url,
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ EXPECT_EQ(2U, file_list->items().size());
+ EXPECT_EQ(1, fake_service_.directory_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetRemainingFileList_Search) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_default_max_results(2);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileList> file_list;
+ fake_service_.Search(
+ "File", // search_query
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ // Do some sanity check.
+ // The number of results is 4 entries. Thus, it should split into two
+ // chunks: 2, and then 2
+ EXPECT_EQ(2U, file_list->items().size());
+
+ // Second page loading.
+ // Keep the next url before releasing the |file_list|.
+ GURL next_url = file_list->next_link();
+
+ error = DRIVE_OTHER_ERROR;
+ file_list.reset();
+ fake_service_.GetRemainingFileList(
+ next_url,
+ test_util::CreateCopyResultCallback(&error, &file_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(file_list);
+
+ EXPECT_EQ(2U, file_list->items().size());
+}
+
+TEST_F(FakeDriveServiceTest, GetRemainingChangeList_GetChangeList) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_default_max_results(2);
+ const int64 old_largest_change_id =
+ fake_service_.about_resource().largest_change_id();
+
+ // Add 5 new directory in the root directory.
+ for (int i = 0; i < 5; ++i) {
+ ASSERT_TRUE(AddNewDirectory(
+ fake_service_.GetRootResourceId(),
+ base::StringPrintf("new directory %d", i)));
+ }
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<ChangeList> change_list;
+ fake_service_.GetChangeList(
+ old_largest_change_id + 1, // start_changestamp
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(change_list);
+
+ // Do some sanity check.
+ // The number of results is 5 entries. Thus, it should split into three
+ // chunks: 2, 2 and then 1.
+ EXPECT_EQ(2U, change_list->items().size());
+ EXPECT_EQ(1, fake_service_.change_list_load_count());
+
+ // Second page loading.
+ // Keep the next url before releasing the |change_list|.
+ GURL next_url = change_list->next_link();
+
+ error = DRIVE_OTHER_ERROR;
+ change_list.reset();
+ fake_service_.GetRemainingChangeList(
+ next_url,
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(change_list);
+
+ EXPECT_EQ(2U, change_list->items().size());
+ EXPECT_EQ(1, fake_service_.change_list_load_count());
+
+ // Third page loading.
+ next_url = change_list->next_link();
+
+ error = DRIVE_OTHER_ERROR;
+ change_list.reset();
+ fake_service_.GetRemainingChangeList(
+ next_url,
+ test_util::CreateCopyResultCallback(&error, &change_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(change_list);
+
+ EXPECT_EQ(1U, change_list->items().size());
+ EXPECT_EQ(1, fake_service_.change_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetAboutResource) {
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<AboutResource> about_resource;
+ fake_service_.GetAboutResource(
+ test_util::CreateCopyResultCallback(&error, &about_resource));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+
+ ASSERT_TRUE(about_resource);
+ // Do some sanity check.
+ EXPECT_EQ(fake_service_.GetRootResourceId(),
+ about_resource->root_folder_id());
+ EXPECT_EQ(1, fake_service_.about_resource_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetAboutResource_Offline) {
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<AboutResource> about_resource;
+ fake_service_.GetAboutResource(
+ test_util::CreateCopyResultCallback(&error, &about_resource));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(about_resource);
+}
+
+TEST_F(FakeDriveServiceTest, GetAppList) {
+ ASSERT_TRUE(fake_service_.LoadAppListForDriveApi(
+ "drive/applist.json"));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<AppList> app_list;
+ fake_service_.GetAppList(
+ test_util::CreateCopyResultCallback(&error, &app_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+
+ ASSERT_TRUE(app_list);
+ EXPECT_EQ(1, fake_service_.app_list_load_count());
+}
+
+TEST_F(FakeDriveServiceTest, GetAppList_Offline) {
+ ASSERT_TRUE(fake_service_.LoadAppListForDriveApi(
+ "drive/applist.json"));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<AppList> app_list;
+ fake_service_.GetAppList(
+ test_util::CreateCopyResultCallback(&error, &app_list));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(app_list);
+}
+
+TEST_F(FakeDriveServiceTest, GetFileResource_ExistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "2_file_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.GetFileResource(
+ kResourceId, test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(entry);
+ // Do some sanity check.
+ EXPECT_EQ(kResourceId, entry->file_id());
+}
+
+TEST_F(FakeDriveServiceTest, GetFileResource_NonexistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "nonexisting_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.GetFileResource(
+ kResourceId, test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ ASSERT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, GetFileResource_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ const std::string kResourceId = "2_file_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.GetFileResource(
+ kResourceId, test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, GetShareUrl) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "2_file_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL share_url;
+ fake_service_.GetShareUrl(
+ kResourceId,
+ GURL(), // embed origin
+ test_util::CreateCopyResultCallback(&error, &share_url));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_FALSE(share_url.is_empty());
+}
+
+TEST_F(FakeDriveServiceTest, DeleteResource_ExistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ // Resource "2_file_resource_id" should now exist.
+ ASSERT_TRUE(Exists("2_file_resource_id"));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ std::string(), // etag
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NO_CONTENT, error);
+ // Resource "2_file_resource_id" should be gone now.
+ EXPECT_FALSE(Exists("2_file_resource_id"));
+
+ error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ std::string(), // etag
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_FALSE(Exists("2_file_resource_id"));
+}
+
+TEST_F(FakeDriveServiceTest, DeleteResource_NonexistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("nonexisting_resource_id",
+ std::string(), // etag
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, DeleteResource_ETagMatch) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ // Resource "2_file_resource_id" should now exist.
+ scoped_ptr<FileResource> entry = FindEntry("2_file_resource_id");
+ ASSERT_TRUE(entry);
+ ASSERT_FALSE(entry->labels().is_trashed());
+ ASSERT_FALSE(entry->etag().empty());
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ entry->etag() + "_mismatch",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_PRECONDITION, error);
+ // Resource "2_file_resource_id" should still exist.
+ EXPECT_TRUE(Exists("2_file_resource_id"));
+
+ error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ entry->etag(),
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_NO_CONTENT, error);
+ // Resource "2_file_resource_id" should be gone now.
+ EXPECT_FALSE(Exists("2_file_resource_id"));
+}
+
+TEST_F(FakeDriveServiceTest, DeleteResource_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ std::string(), // etag
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+}
+
+TEST_F(FakeDriveServiceTest, DeleteResource_Forbidden) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission(
+ "2_file_resource_id", google_apis::drive::PERMISSION_ROLE_READER));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.DeleteResource("2_file_resource_id",
+ std::string(), // etag
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_FORBIDDEN, error);
+}
+
+TEST_F(FakeDriveServiceTest, TrashResource_ExistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ // Resource "2_file_resource_id" should now exist.
+ ASSERT_TRUE(Exists("2_file_resource_id"));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.TrashResource("2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ // Resource "2_file_resource_id" should be gone now.
+ EXPECT_FALSE(Exists("2_file_resource_id"));
+
+ error = DRIVE_OTHER_ERROR;
+ fake_service_.TrashResource("2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_FALSE(Exists("2_file_resource_id"));
+}
+
+TEST_F(FakeDriveServiceTest, TrashResource_NonexistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.TrashResource("nonexisting_resource_id",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, TrashResource_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.TrashResource("2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+}
+
+TEST_F(FakeDriveServiceTest, TrashResource_Forbidden) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission(
+ "2_file_resource_id", google_apis::drive::PERMISSION_ROLE_READER));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.TrashResource("2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_FORBIDDEN, error);
+}
+
+TEST_F(FakeDriveServiceTest, DownloadFile_ExistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ std::vector<test_util::ProgressInfo> download_progress_values;
+
+ const base::FilePath kOutputFilePath =
+ temp_dir.path().AppendASCII("whatever.txt");
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ base::FilePath output_file_path;
+ test_util::TestGetContentCallback get_content_callback;
+ fake_service_.DownloadFile(
+ kOutputFilePath,
+ "2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error, &output_file_path),
+ get_content_callback.callback(),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &download_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_EQ(output_file_path, kOutputFilePath);
+ std::string content;
+ ASSERT_TRUE(base::ReadFileToString(output_file_path, &content));
+ EXPECT_EQ("This is some test content.", content);
+ ASSERT_TRUE(!download_progress_values.empty());
+ EXPECT_TRUE(base::STLIsSorted(download_progress_values));
+ EXPECT_LE(0, download_progress_values.front().first);
+ EXPECT_GE(26, download_progress_values.back().first);
+ EXPECT_EQ(content, get_content_callback.GetConcatenatedData());
+}
+
+TEST_F(FakeDriveServiceTest, DownloadFile_NonexistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const base::FilePath kOutputFilePath =
+ temp_dir.path().AppendASCII("whatever.txt");
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ base::FilePath output_file_path;
+ fake_service_.DownloadFile(
+ kOutputFilePath,
+ "non_existent_file_resource_id",
+ test_util::CreateCopyResultCallback(&error, &output_file_path),
+ GetContentCallback(),
+ ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, DownloadFile_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ const base::FilePath kOutputFilePath =
+ temp_dir.path().AppendASCII("whatever.txt");
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ base::FilePath output_file_path;
+ fake_service_.DownloadFile(
+ kOutputFilePath,
+ "2_file_resource_id",
+ test_util::CreateCopyResultCallback(&error, &output_file_path),
+ GetContentCallback(),
+ ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+}
+
+TEST_F(FakeDriveServiceTest, CopyResource) {
+ const base::Time::Exploded kModifiedDate = {2012, 7, 0, 19, 15, 59, 13, 123};
+
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "2_file_resource_id";
+ const std::string kParentResourceId = "2_folder_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.CopyResource(
+ kResourceId,
+ kParentResourceId,
+ "new title",
+ base::Time::FromUTCExploded(kModifiedDate),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(entry);
+ // The copied entry should have the new resource ID and the title.
+ EXPECT_NE(kResourceId, entry->file_id());
+ EXPECT_EQ("new title", entry->title());
+ EXPECT_EQ(base::Time::FromUTCExploded(kModifiedDate), entry->modified_date());
+ EXPECT_TRUE(HasParent(entry->file_id(), kParentResourceId));
+ // Should be incremented as a new hosted document was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, CopyResource_NonExisting) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "nonexisting_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.CopyResource(
+ kResourceId,
+ "1_folder_resource_id",
+ "new title",
+ base::Time(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, CopyResource_EmptyParentResourceId) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "2_file_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.CopyResource(
+ kResourceId,
+ std::string(),
+ "new title",
+ base::Time(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(entry);
+ // The copied entry should have the new resource ID and the title.
+ EXPECT_NE(kResourceId, entry->file_id());
+ EXPECT_EQ("new title", entry->title());
+ EXPECT_TRUE(HasParent(kResourceId, fake_service_.GetRootResourceId()));
+ // Should be incremented as a new hosted document was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, CopyResource_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ const std::string kResourceId = "2_file_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.CopyResource(
+ kResourceId,
+ "1_folder_resource_id",
+ "new title",
+ base::Time(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, UpdateResource) {
+ const base::Time::Exploded kModifiedDate = {2012, 7, 0, 19, 15, 59, 13, 123};
+ const base::Time::Exploded kViewedDate = {2013, 8, 1, 20, 16, 00, 14, 234};
+
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "2_file_resource_id";
+ const std::string kParentResourceId = "2_folder_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.UpdateResource(
+ kResourceId, kParentResourceId, "new title",
+ base::Time::FromUTCExploded(kModifiedDate),
+ base::Time::FromUTCExploded(kViewedDate),
+ google_apis::drive::Properties(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(entry);
+ // The updated entry should have the new title.
+ EXPECT_EQ(kResourceId, entry->file_id());
+ EXPECT_EQ("new title", entry->title());
+ EXPECT_EQ(base::Time::FromUTCExploded(kModifiedDate),
+ entry->modified_date());
+ EXPECT_EQ(base::Time::FromUTCExploded(kViewedDate),
+ entry->last_viewed_by_me_date());
+ EXPECT_TRUE(HasParent(kResourceId, kParentResourceId));
+ // Should be incremented as a new hosted document was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, UpdateResource_NonExisting) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "nonexisting_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.UpdateResource(
+ kResourceId, "1_folder_resource_id", "new title", base::Time(),
+ base::Time(), google_apis::drive::Properties(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, UpdateResource_EmptyParentResourceId) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "2_file_resource_id";
+
+ // Just make sure that the resource is under root.
+ ASSERT_TRUE(HasParent(kResourceId, "fake_root"));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.UpdateResource(
+ kResourceId, std::string(), "new title", base::Time(), base::Time(),
+ google_apis::drive::Properties(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(entry);
+ // The updated entry should have the new title.
+ EXPECT_EQ(kResourceId, entry->file_id());
+ EXPECT_EQ("new title", entry->title());
+ EXPECT_TRUE(HasParent(kResourceId, "fake_root"));
+ // Should be incremented as a new hosted document was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, UpdateResource_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ const std::string kResourceId = "2_file_resource_id";
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.UpdateResource(
+ kResourceId, std::string(), "new title", base::Time(), base::Time(),
+ google_apis::drive::Properties(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, UpdateResource_Forbidden) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "2_file_resource_id";
+ EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission(
+ kResourceId, google_apis::drive::PERMISSION_ROLE_READER));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.UpdateResource(
+ kResourceId, std::string(), "new title", base::Time(), base::Time(),
+ google_apis::drive::Properties(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_FORBIDDEN, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, AddResourceToDirectory_FileInRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "2_file_resource_id";
+ const std::string kOldParentResourceId = fake_service_.GetRootResourceId();
+ const std::string kNewParentResourceId = "1_folder_resource_id";
+
+ // Here's the original parent link.
+ EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId));
+ EXPECT_FALSE(HasParent(kResourceId, kNewParentResourceId));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.AddResourceToDirectory(
+ kNewParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+
+ // The parent link should now be changed.
+ EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId));
+ EXPECT_TRUE(HasParent(kResourceId, kNewParentResourceId));
+ // Should be incremented as a file was moved.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, AddResourceToDirectory_FileInNonRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "subdirectory_file_1_id";
+ const std::string kOldParentResourceId = "1_folder_resource_id";
+ const std::string kNewParentResourceId = "2_folder_resource_id";
+
+ // Here's the original parent link.
+ EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId));
+ EXPECT_FALSE(HasParent(kResourceId, kNewParentResourceId));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.AddResourceToDirectory(
+ kNewParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+
+ // The parent link should now be changed.
+ EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId));
+ EXPECT_TRUE(HasParent(kResourceId, kNewParentResourceId));
+ // Should be incremented as a file was moved.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, AddResourceToDirectory_NonexistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "nonexisting_file";
+ const std::string kNewParentResourceId = "1_folder_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.AddResourceToDirectory(
+ kNewParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, AddResourceToDirectory_OrphanFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "1_orphanfile_resource_id";
+ const std::string kNewParentResourceId = "1_folder_resource_id";
+
+ // The file does not belong to any directory, even to the root.
+ EXPECT_FALSE(HasParent(kResourceId, kNewParentResourceId));
+ EXPECT_FALSE(HasParent(kResourceId, fake_service_.GetRootResourceId()));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.AddResourceToDirectory(
+ kNewParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+
+ // The parent link should now be changed.
+ EXPECT_TRUE(HasParent(kResourceId, kNewParentResourceId));
+ EXPECT_FALSE(HasParent(kResourceId, fake_service_.GetRootResourceId()));
+ // Should be incremented as a file was moved.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, AddResourceToDirectory_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ const std::string kResourceId = "2_file_resource_id";
+ const std::string kNewParentResourceId = "1_folder_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.AddResourceToDirectory(
+ kNewParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+}
+
+TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_ExistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kResourceId = "subdirectory_file_1_id";
+ const std::string kParentResourceId = "1_folder_resource_id";
+
+ scoped_ptr<FileResource> entry = FindEntry(kResourceId);
+ ASSERT_TRUE(entry);
+ // The entry should have a parent now.
+ ASSERT_FALSE(entry->parents().empty());
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.RemoveResourceFromDirectory(
+ kParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NO_CONTENT, error);
+
+ entry = FindEntry(kResourceId);
+ ASSERT_TRUE(entry);
+ // The entry should have no parent now.
+ ASSERT_TRUE(entry->parents().empty());
+ // Should be incremented as a file was moved to the root directory.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_NonexistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "nonexisting_file";
+ const std::string kParentResourceId = "1_folder_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.RemoveResourceFromDirectory(
+ kParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_OrphanFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "1_orphanfile_resource_id";
+ const std::string kParentResourceId = fake_service_.GetRootResourceId();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.RemoveResourceFromDirectory(
+ kParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+}
+
+TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ const std::string kResourceId = "subdirectory_file_1_id";
+ const std::string kParentResourceId = "1_folder_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ fake_service_.RemoveResourceFromDirectory(
+ kParentResourceId,
+ kResourceId,
+ test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+}
+
+TEST_F(FakeDriveServiceTest, AddNewDirectory_EmptyParent) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewDirectory(
+ std::string(), "new directory", AddNewDirectoryOptions(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->IsDirectory());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ("new directory", entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId()));
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewDirectory_ToRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewDirectory(
+ fake_service_.GetRootResourceId(), "new directory",
+ AddNewDirectoryOptions(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->IsDirectory());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ("new directory", entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId()));
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewDirectory_ToRootDirectoryOnEmptyFileSystem) {
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewDirectory(
+ fake_service_.GetRootResourceId(), "new directory",
+ AddNewDirectoryOptions(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->IsDirectory());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ("new directory", entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId()));
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewDirectory_ToNonRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kParentResourceId = "1_folder_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewDirectory(
+ kParentResourceId, "new directory", AddNewDirectoryOptions(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->IsDirectory());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ("new directory", entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), kParentResourceId));
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewDirectory_ToNonexistingDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kParentResourceId = "nonexisting_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewDirectory(
+ kParentResourceId, "new directory", AddNewDirectoryOptions(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, AddNewDirectory_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewDirectory(
+ fake_service_.GetRootResourceId(), "new directory",
+ AddNewDirectoryOptions(),
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUploadNewFile_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadNewFile(
+ "test/foo", 13, "1_folder_resource_id", "new file.foo",
+ UploadNewFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUploadNewFile_NotFound) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadNewFile(
+ "test/foo", 13, "non_existent", "new file.foo", UploadNewFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUploadNewFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadNewFile(
+ "test/foo", 13, "1_folder_resource_id", "new file.foo",
+ UploadNewFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_FALSE(upload_location.is_empty());
+ EXPECT_NE(GURL("https://1_folder_resumable_create_media_link?mode=newfile"),
+ upload_location);
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadExistingFile(
+ "test/foo", 13, "2_file_resource_id", UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_Forbidden) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission(
+ "2_file_resource_id", google_apis::drive::PERMISSION_ROLE_READER));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadExistingFile(
+ "test/foo", 13, "2_file_resource_id", UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_FORBIDDEN, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_NotFound) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadExistingFile(
+ "test/foo", 13, "non_existent", UploadExistingFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_WrongETag) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ UploadExistingFileOptions options;
+ options.etag = "invalid_etag";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadExistingFile(
+ "text/plain",
+ 13,
+ "2_file_resource_id",
+ options,
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_PRECONDITION, error);
+ EXPECT_TRUE(upload_location.is_empty());
+}
+
+TEST_F(FakeDriveServiceTest, InitiateUpload_ExistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ scoped_ptr<FileResource> entry = FindEntry("2_file_resource_id");
+ ASSERT_TRUE(entry);
+
+ UploadExistingFileOptions options;
+ options.etag = entry->etag();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadExistingFile(
+ "text/plain",
+ 13,
+ "2_file_resource_id",
+ options,
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_TRUE(upload_location.is_valid());
+}
+
+TEST_F(FakeDriveServiceTest, ResumeUpload_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadNewFile(
+ "test/foo", 15, "1_folder_resource_id", "new file.foo",
+ UploadNewFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_FALSE(upload_location.is_empty());
+ EXPECT_NE(GURL("https://1_folder_resumable_create_media_link"),
+ upload_location);
+
+ fake_service_.set_offline(true);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> entry;
+ fake_service_.ResumeUpload(
+ upload_location,
+ 0, 13, 15, "test/foo",
+ base::FilePath(),
+ test_util::CreateCopyResultCallback(&response, &entry),
+ ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, response.code);
+ EXPECT_FALSE(entry.get());
+}
+
+TEST_F(FakeDriveServiceTest, ResumeUpload_NotFound) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadNewFile(
+ "test/foo", 15, "1_folder_resource_id", "new file.foo",
+ UploadNewFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(HTTP_SUCCESS, error);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> entry;
+ fake_service_.ResumeUpload(
+ GURL("https://foo.com/"),
+ 0, 13, 15, "test/foo",
+ base::FilePath(),
+ test_util::CreateCopyResultCallback(&response, &entry),
+ ProgressCallback());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, response.code);
+ EXPECT_FALSE(entry.get());
+}
+
+TEST_F(FakeDriveServiceTest, ResumeUpload_ExistingFile) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath local_file_path =
+ temp_dir.path().Append(FILE_PATH_LITERAL("File 1.txt"));
+ std::string contents("hogefugapiyo");
+ ASSERT_TRUE(test_util::WriteStringToFile(local_file_path, contents));
+
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ scoped_ptr<FileResource> entry = FindEntry("2_file_resource_id");
+ ASSERT_TRUE(entry);
+
+ UploadExistingFileOptions options;
+ options.etag = entry->etag();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadExistingFile(
+ "text/plain",
+ contents.size(),
+ "2_file_resource_id",
+ options,
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(HTTP_SUCCESS, error);
+
+ UploadRangeResponse response;
+ entry.reset();
+ std::vector<test_util::ProgressInfo> upload_progress_values;
+ fake_service_.ResumeUpload(
+ upload_location,
+ 0, contents.size() / 2, contents.size(), "text/plain",
+ local_file_path,
+ test_util::CreateCopyResultCallback(&response, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code);
+ EXPECT_FALSE(entry.get());
+ ASSERT_TRUE(!upload_progress_values.empty());
+ EXPECT_TRUE(base::STLIsSorted(upload_progress_values));
+ EXPECT_LE(0, upload_progress_values.front().first);
+ EXPECT_GE(static_cast<int64>(contents.size() / 2),
+ upload_progress_values.back().first);
+
+ upload_progress_values.clear();
+ fake_service_.ResumeUpload(
+ upload_location,
+ contents.size() / 2, contents.size(), contents.size(), "text/plain",
+ local_file_path,
+ test_util::CreateCopyResultCallback(&response, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, response.code);
+ EXPECT_TRUE(entry.get());
+ EXPECT_EQ(static_cast<int64>(contents.size()), entry->file_size());
+ EXPECT_TRUE(Exists(entry->file_id()));
+ ASSERT_TRUE(!upload_progress_values.empty());
+ EXPECT_TRUE(base::STLIsSorted(upload_progress_values));
+ EXPECT_LE(0, upload_progress_values.front().first);
+ EXPECT_GE(static_cast<int64>(contents.size() - contents.size() / 2),
+ upload_progress_values.back().first);
+ EXPECT_EQ(base::MD5String(contents), entry->md5_checksum());
+}
+
+TEST_F(FakeDriveServiceTest, ResumeUpload_NewFile) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath local_file_path =
+ temp_dir.path().Append(FILE_PATH_LITERAL("new file.foo"));
+ std::string contents("hogefugapiyo");
+ ASSERT_TRUE(test_util::WriteStringToFile(local_file_path, contents));
+
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ GURL upload_location;
+ fake_service_.InitiateUploadNewFile(
+ "test/foo", contents.size(), "1_folder_resource_id", "new file.foo",
+ UploadNewFileOptions(),
+ test_util::CreateCopyResultCallback(&error, &upload_location));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ EXPECT_FALSE(upload_location.is_empty());
+ EXPECT_NE(GURL("https://1_folder_resumable_create_media_link"),
+ upload_location);
+
+ UploadRangeResponse response;
+ scoped_ptr<FileResource> entry;
+ std::vector<test_util::ProgressInfo> upload_progress_values;
+ fake_service_.ResumeUpload(
+ upload_location,
+ 0, contents.size() / 2, contents.size(), "test/foo",
+ local_file_path,
+ test_util::CreateCopyResultCallback(&response, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code);
+ EXPECT_FALSE(entry.get());
+ ASSERT_TRUE(!upload_progress_values.empty());
+ EXPECT_TRUE(base::STLIsSorted(upload_progress_values));
+ EXPECT_LE(0, upload_progress_values.front().first);
+ EXPECT_GE(static_cast<int64>(contents.size() / 2),
+ upload_progress_values.back().first);
+
+ upload_progress_values.clear();
+ fake_service_.ResumeUpload(
+ upload_location,
+ contents.size() / 2, contents.size(), contents.size(), "test/foo",
+ local_file_path,
+ test_util::CreateCopyResultCallback(&response, &entry),
+ base::Bind(&test_util::AppendProgressCallbackResult,
+ &upload_progress_values));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, response.code);
+ EXPECT_TRUE(entry.get());
+ EXPECT_EQ(static_cast<int64>(contents.size()), entry->file_size());
+ EXPECT_TRUE(Exists(entry->file_id()));
+ ASSERT_TRUE(!upload_progress_values.empty());
+ EXPECT_TRUE(base::STLIsSorted(upload_progress_values));
+ EXPECT_LE(0, upload_progress_values.front().first);
+ EXPECT_GE(static_cast<int64>(contents.size() - contents.size() / 2),
+ upload_progress_values.back().first);
+ EXPECT_EQ(base::MD5String(contents), entry->md5_checksum());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewFile_ToRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kContentType = "text/plain";
+ const std::string kContentData = "This is some test content.";
+ const std::string kTitle = "new file";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewFile(
+ kContentType,
+ kContentData,
+ fake_service_.GetRootResourceId(),
+ kTitle,
+ false, // shared_with_me
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kContentType, entry->mime_type());
+ EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ(kTitle, entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId()));
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+ EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewFile_ToRootDirectoryOnEmptyFileSystem) {
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kContentType = "text/plain";
+ const std::string kContentData = "This is some test content.";
+ const std::string kTitle = "new file";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewFile(
+ kContentType,
+ kContentData,
+ fake_service_.GetRootResourceId(),
+ kTitle,
+ false, // shared_with_me
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kContentType, entry->mime_type());
+ EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ(kTitle, entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId()));
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+ EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewFile_ToNonRootDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ const std::string kContentType = "text/plain";
+ const std::string kContentData = "This is some test content.";
+ const std::string kTitle = "new file";
+ const std::string kParentResourceId = "1_folder_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewFile(
+ kContentType,
+ kContentData,
+ kParentResourceId,
+ kTitle,
+ false, // shared_with_me
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kContentType, entry->mime_type());
+ EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ(kTitle, entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), kParentResourceId));
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+ EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum());
+}
+
+TEST_F(FakeDriveServiceTest, AddNewFile_ToNonexistingDirectory) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kContentType = "text/plain";
+ const std::string kContentData = "This is some test content.";
+ const std::string kTitle = "new file";
+ const std::string kParentResourceId = "nonexisting_resource_id";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewFile(
+ kContentType,
+ kContentData,
+ kParentResourceId,
+ kTitle,
+ false, // shared_with_me
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, AddNewFile_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ const std::string kContentType = "text/plain";
+ const std::string kContentData = "This is some test content.";
+ const std::string kTitle = "new file";
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewFile(
+ kContentType,
+ kContentData,
+ fake_service_.GetRootResourceId(),
+ kTitle,
+ false, // shared_with_me
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, AddNewFile_SharedWithMeLabel) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kContentType = "text/plain";
+ const std::string kContentData = "This is some test content.";
+ const std::string kTitle = "new file";
+
+ int64 old_largest_change_id = GetLargestChangeByAboutResource();
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.AddNewFile(
+ kContentType,
+ kContentData,
+ fake_service_.GetRootResourceId(),
+ kTitle,
+ true, // shared_with_me
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(kContentType, entry->mime_type());
+ EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size());
+ EXPECT_EQ("resource_id_1", entry->file_id());
+ EXPECT_EQ(kTitle, entry->title());
+ EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId()));
+ EXPECT_FALSE(entry->shared_with_me_date().is_null());
+ // Should be incremented as a new directory was created.
+ EXPECT_EQ(old_largest_change_id + 1,
+ fake_service_.about_resource().largest_change_id());
+ EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource());
+ EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum());
+}
+
+TEST_F(FakeDriveServiceTest, SetLastModifiedTime_ExistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "2_file_resource_id";
+ base::Time time;
+ ASSERT_TRUE(base::Time::FromString("1 April 2013 12:34:56", &time));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.SetLastModifiedTime(
+ kResourceId,
+ time,
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_SUCCESS, error);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(time, entry->modified_date());
+}
+
+TEST_F(FakeDriveServiceTest, SetLastModifiedTime_NonexistingFile) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+
+ const std::string kResourceId = "nonexisting_resource_id";
+ base::Time time;
+ ASSERT_TRUE(base::Time::FromString("1 April 2013 12:34:56", &time));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.SetLastModifiedTime(
+ kResourceId,
+ time,
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(HTTP_NOT_FOUND, error);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FakeDriveServiceTest, SetLastModifiedTime_Offline) {
+ ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_));
+ fake_service_.set_offline(true);
+
+ const std::string kResourceId = "2_file_resource_id";
+ base::Time time;
+ ASSERT_TRUE(base::Time::FromString("1 April 2013 12:34:56", &time));
+
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+ fake_service_.SetLastModifiedTime(
+ kResourceId,
+ time,
+ test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(DRIVE_NO_CONNECTION, error);
+ EXPECT_FALSE(entry);
+}
+
+} // namespace
+
+} // namespace drive
diff --git a/components/drive/service/test_util.cc b/components/drive/service/test_util.cc
new file mode 100644
index 0000000..42c54f1
--- /dev/null
+++ b/components/drive/service/test_util.cc
@@ -0,0 +1,193 @@
+// Copyright 2014 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 "components/drive/service/test_util.h"
+
+#include "base/run_loop.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+
+using google_apis::FileResource;
+using google_apis::DRIVE_OTHER_ERROR;
+using google_apis::DriveApiErrorCode;
+using google_apis::HTTP_CREATED;
+
+namespace drive {
+namespace test_util {
+
+bool SetUpTestEntries(FakeDriveService* drive_service) {
+ DriveApiErrorCode error = DRIVE_OTHER_ERROR;
+ scoped_ptr<FileResource> entry;
+
+ drive_service->AddNewFileWithResourceId(
+ "2_file_resource_id",
+ "audio/mpeg",
+ "This is some test content.",
+ drive_service->GetRootResourceId(),
+ "File 1.txt",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "slash_file_resource_id",
+ "audio/mpeg",
+ "This is some test content.",
+ drive_service->GetRootResourceId(),
+ "Slash / in file 1.txt",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "3_file_resource_id",
+ "audio/mpeg",
+ "This is some test content.",
+ drive_service->GetRootResourceId(),
+ "Duplicate Name.txt",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "4_file_resource_id",
+ "audio/mpeg",
+ "This is some test content.",
+ drive_service->GetRootResourceId(),
+ "Duplicate Name.txt",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "5_document_resource_id",
+ util::kGoogleDocumentMimeType,
+ std::string(),
+ drive_service->GetRootResourceId(),
+ "Document 1 excludeDir-test",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "1_folder_resource_id",
+ util::kDriveFolderMimeType,
+ std::string(),
+ drive_service->GetRootResourceId(),
+ "Directory 1",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "subdirectory_file_1_id",
+ "audio/mpeg",
+ "This is some test content.",
+ "1_folder_resource_id",
+ "SubDirectory File 1.txt",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "subdirectory_unowned_file_1_id",
+ "audio/mpeg",
+ "This is some test content.",
+ "1_folder_resource_id",
+ "Shared to The Account Owner.txt",
+ true, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewDirectoryWithResourceId(
+ "sub_dir_folder_resource_id", "1_folder_resource_id",
+ "Sub Directory Folder", AddNewDirectoryOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewDirectoryWithResourceId(
+ "sub_sub_directory_folder_id", "sub_dir_folder_resource_id",
+ "Sub Sub Directory Folder", AddNewDirectoryOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewDirectoryWithResourceId(
+ "slash_dir_folder_resource_id", drive_service->GetRootResourceId(),
+ "Slash / in directory", AddNewDirectoryOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "slash_subdir_file",
+ "audio/mpeg",
+ "This is some test content.",
+ "slash_dir_folder_resource_id",
+ "Slash SubDir File.txt",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewDirectoryWithResourceId(
+ "sub_dir_folder_2_self_link", drive_service->GetRootResourceId(),
+ "Directory 2 excludeDir-test", AddNewDirectoryOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "1_orphanfile_resource_id",
+ "text/plain",
+ "This is some test content.",
+ std::string(),
+ "Orphan File 1.txt",
+ true, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ drive_service->AddNewFileWithResourceId(
+ "orphan_doc_1",
+ util::kGoogleDocumentMimeType,
+ std::string(),
+ std::string(),
+ "Orphan Document",
+ true, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ if (error != HTTP_CREATED)
+ return false;
+
+ return true;
+}
+
+} // namespace test_util
+} // namespace drive
diff --git a/components/drive/service/test_util.h b/components/drive/service/test_util.h
new file mode 100644
index 0000000..9cd25ae
--- /dev/null
+++ b/components/drive/service/test_util.h
@@ -0,0 +1,19 @@
+// Copyright 2014 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.
+
+#ifndef COMPONENTS_DRIVE_SERVICE_TEST_UTIL_H_
+#define COMPONENTS_DRIVE_SERVICE_TEST_UTIL_H_
+
+namespace drive {
+
+class FakeDriveService;
+
+namespace test_util {
+
+bool SetUpTestEntries(FakeDriveService* drive_service);
+
+} // namespace test_util
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SERVICE_TEST_UTIL_H_