1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/drive/fake_drive_service.h"
9 #include "base/file_util.h"
10 #include "base/json/json_string_value_serializer.h"
11 #include "base/logging.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_tokenizer.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "chrome/browser/drive/drive_api_util.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "google_apis/drive/drive_api_parser.h"
24 #include "google_apis/drive/test_util.h"
25 #include "net/base/escape.h"
26 #include "net/base/url_util.h"
28 using content::BrowserThread;
29 using google_apis::AboutResource;
30 using google_apis::AboutResourceCallback;
31 using google_apis::AppList;
32 using google_apis::AppListCallback;
33 using google_apis::AuthStatusCallback;
34 using google_apis::AuthorizeAppCallback;
35 using google_apis::CancelCallback;
36 using google_apis::ChangeList;
37 using google_apis::ChangeListCallback;
38 using google_apis::ChangeResource;
39 using google_apis::DownloadActionCallback;
40 using google_apis::EntryActionCallback;
41 using google_apis::FileList;
42 using google_apis::FileListCallback;
43 using google_apis::FileResource;
44 using google_apis::FileResourceCallback;
45 using google_apis::GDATA_FILE_ERROR;
46 using google_apis::GDATA_NO_CONNECTION;
47 using google_apis::GDATA_OTHER_ERROR;
48 using google_apis::GDataErrorCode;
49 using google_apis::GetContentCallback;
50 using google_apis::GetShareUrlCallback;
51 using google_apis::HTTP_BAD_REQUEST;
52 using google_apis::HTTP_CREATED;
53 using google_apis::HTTP_FORBIDDEN;
54 using google_apis::HTTP_NOT_FOUND;
55 using google_apis::HTTP_NO_CONTENT;
56 using google_apis::HTTP_PRECONDITION;
57 using google_apis::HTTP_RESUME_INCOMPLETE;
58 using google_apis::HTTP_SUCCESS;
59 using google_apis::InitiateUploadCallback;
60 using google_apis::ParentReference;
61 using google_apis::ProgressCallback;
62 using google_apis::UploadRangeResponse;
63 using google_apis::drive::UploadRangeCallback;
64 namespace test_util = google_apis::test_util;
69 // Returns true if the entry matches with the search query.
70 // Supports queries consist of following format.
71 // - Phrases quoted by double/single quotes
72 // - AND search for multiple words/phrases segmented by space
73 // - Limited attribute search. Only "title:" is supported.
74 bool EntryMatchWithQuery(const ChangeResource& entry,
75 const std::string& query) {
76 base::StringTokenizer tokenizer(query, " ");
77 tokenizer.set_quote_chars("\"'");
78 while (tokenizer.GetNext()) {
79 std::string key, value;
80 const std::string& token = tokenizer.token();
81 if (token.find(':') == std::string::npos) {
82 base::TrimString(token, "\"'", &value);
84 base::StringTokenizer key_value(token, ":");
85 key_value.set_quote_chars("\"'");
86 if (!key_value.GetNext())
88 key = key_value.token();
89 if (!key_value.GetNext())
91 base::TrimString(key_value.token(), "\"'", &value);
94 // TODO(peria): Deal with other attributes than title.
95 if (!key.empty() && key != "title")
97 // Search query in the title.
99 entry.file()->title().find(value) == std::string::npos)
105 void ScheduleUploadRangeCallback(const UploadRangeCallback& callback,
106 int64 start_position,
108 GDataErrorCode error,
109 scoped_ptr<FileResource> entry) {
110 base::MessageLoop::current()->PostTask(
113 UploadRangeResponse(error,
116 base::Passed(&entry)));
119 void FileListCallbackAdapter(const FileListCallback& callback,
120 GDataErrorCode error,
121 scoped_ptr<ChangeList> change_list) {
122 scoped_ptr<FileList> file_list;
124 file_list.reset(new FileList);
125 file_list->set_next_link(change_list->next_link());
126 for (size_t i = 0; i < change_list->items().size(); ++i) {
127 const ChangeResource& entry = *change_list->items()[i];
129 file_list->mutable_items()->push_back(new FileResource(*entry.file()));
132 callback.Run(error, file_list.Pass());
135 bool UserHasWriteAccess(google_apis::drive::PermissionRole user_permission) {
136 switch (user_permission) {
137 case google_apis::drive::PERMISSION_ROLE_OWNER:
138 case google_apis::drive::PERMISSION_ROLE_WRITER:
140 case google_apis::drive::PERMISSION_ROLE_READER:
141 case google_apis::drive::PERMISSION_ROLE_COMMENTER:
149 struct FakeDriveService::EntryInfo {
150 EntryInfo() : user_permission(google_apis::drive::PERMISSION_ROLE_OWNER) {}
152 google_apis::ChangeResource change_resource;
154 std::string content_data;
156 // Behaves in the same way as "userPermission" described in
157 // https://developers.google.com/drive/v2/reference/files
158 google_apis::drive::PermissionRole user_permission;
161 struct FakeDriveService::UploadSession {
162 std::string content_type;
163 int64 content_length;
164 std::string parent_resource_id;
165 std::string resource_id;
176 std::string content_type,
177 int64 content_length,
178 std::string parent_resource_id,
179 std::string resource_id,
182 : content_type(content_type),
183 content_length(content_length),
184 parent_resource_id(parent_resource_id),
185 resource_id(resource_id),
192 FakeDriveService::FakeDriveService()
193 : about_resource_(new AboutResource),
194 published_date_seq_(0),
195 next_upload_sequence_number_(0),
196 default_max_results_(0),
197 resource_id_count_(0),
198 file_list_load_count_(0),
199 change_list_load_count_(0),
200 directory_load_count_(0),
201 about_resource_load_count_(0),
202 app_list_load_count_(0),
203 blocked_file_list_load_count_(0),
205 never_return_all_file_list_(false),
206 share_url_base_("https://share_url/") {
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
209 about_resource_->set_largest_change_id(654321);
210 about_resource_->set_quota_bytes_total(9876543210);
211 about_resource_->set_quota_bytes_used(6789012345);
212 about_resource_->set_root_folder_id(GetRootResourceId());
215 FakeDriveService::~FakeDriveService() {
216 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
217 STLDeleteValues(&entries_);
220 bool FakeDriveService::LoadAppListForDriveApi(
221 const std::string& relative_path) {
222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
224 // Load JSON data, which must be a dictionary.
225 scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path);
226 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
227 app_info_value_.reset(
228 static_cast<base::DictionaryValue*>(value.release()));
229 return app_info_value_;
232 void FakeDriveService::AddApp(const std::string& app_id,
233 const std::string& app_name,
234 const std::string& product_id,
235 const std::string& create_url) {
236 if (app_json_template_.empty()) {
237 base::FilePath path =
238 test_util::GetTestFilePath("drive/applist_app_template.json");
239 CHECK(base::ReadFileToString(path, &app_json_template_));
242 std::string app_json = app_json_template_;
243 ReplaceSubstringsAfterOffset(&app_json, 0, "$AppId", app_id);
244 ReplaceSubstringsAfterOffset(&app_json, 0, "$AppName", app_name);
245 ReplaceSubstringsAfterOffset(&app_json, 0, "$ProductId", product_id);
246 ReplaceSubstringsAfterOffset(&app_json, 0, "$CreateUrl", create_url);
248 JSONStringValueSerializer json(app_json);
249 std::string error_message;
250 scoped_ptr<base::Value> value(json.Deserialize(NULL, &error_message));
251 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
253 base::ListValue* item_list;
254 CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
255 item_list->Append(value.release());
258 void FakeDriveService::RemoveAppByProductId(const std::string& product_id) {
259 base::ListValue* item_list;
260 CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
261 for (size_t i = 0; i < item_list->GetSize(); ++i) {
262 base::DictionaryValue* item;
263 CHECK(item_list->GetDictionary(i, &item));
264 const char kKeyProductId[] = "productId";
265 std::string item_product_id;
266 if (item->GetStringWithoutPathExpansion(kKeyProductId, &item_product_id) &&
267 product_id == item_product_id) {
268 item_list->Remove(i, NULL);
274 bool FakeDriveService::HasApp(const std::string& app_id) const {
275 base::ListValue* item_list;
276 CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
277 for (size_t i = 0; i < item_list->GetSize(); ++i) {
278 base::DictionaryValue* item;
279 CHECK(item_list->GetDictionary(i, &item));
280 const char kKeyId[] = "id";
282 if (item->GetStringWithoutPathExpansion(kKeyId, &item_id) &&
291 void FakeDriveService::SetQuotaValue(int64 used, int64 total) {
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
294 about_resource_->set_quota_bytes_used(used);
295 about_resource_->set_quota_bytes_total(total);
298 GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
299 return GURL("https://fake_server/" + net::EscapePath(resource_id));
302 void FakeDriveService::Initialize(const std::string& account_id) {
303 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
310 void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
314 bool FakeDriveService::CanSendRequest() const {
315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
319 bool FakeDriveService::HasAccessToken() const {
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324 void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) {
325 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
326 DCHECK(!callback.is_null());
327 callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
330 bool FakeDriveService::HasRefreshToken() const {
331 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
335 void FakeDriveService::ClearAccessToken() {
336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
339 void FakeDriveService::ClearRefreshToken() {
340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
343 std::string FakeDriveService::GetRootResourceId() const {
347 CancelCallback FakeDriveService::GetAllFileList(
348 const FileListCallback& callback) {
349 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
350 DCHECK(!callback.is_null());
352 if (never_return_all_file_list_) {
353 ++blocked_file_list_load_count_;
354 return CancelCallback();
357 GetChangeListInternal(0, // start changestamp
358 std::string(), // empty search query
359 std::string(), // no directory resource id,
361 default_max_results_,
362 &file_list_load_count_,
363 base::Bind(&FileListCallbackAdapter, callback));
364 return CancelCallback();
367 CancelCallback FakeDriveService::GetFileListInDirectory(
368 const std::string& directory_resource_id,
369 const FileListCallback& callback) {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
371 DCHECK(!directory_resource_id.empty());
372 DCHECK(!callback.is_null());
374 GetChangeListInternal(0, // start changestamp
375 std::string(), // empty search query
376 directory_resource_id,
378 default_max_results_,
379 &directory_load_count_,
380 base::Bind(&FileListCallbackAdapter, callback));
381 return CancelCallback();
384 CancelCallback FakeDriveService::Search(
385 const std::string& search_query,
386 const FileListCallback& callback) {
387 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
388 DCHECK(!search_query.empty());
389 DCHECK(!callback.is_null());
391 GetChangeListInternal(0, // start changestamp
393 std::string(), // no directory resource id,
395 default_max_results_,
397 base::Bind(&FileListCallbackAdapter, callback));
398 return CancelCallback();
401 CancelCallback FakeDriveService::SearchByTitle(
402 const std::string& title,
403 const std::string& directory_resource_id,
404 const FileListCallback& callback) {
405 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
406 DCHECK(!title.empty());
407 DCHECK(!callback.is_null());
409 // Note: the search implementation here doesn't support quotation unescape,
410 // so don't escape here.
411 GetChangeListInternal(0, // start changestamp
412 base::StringPrintf("title:'%s'", title.c_str()),
413 directory_resource_id,
415 default_max_results_,
417 base::Bind(&FileListCallbackAdapter, callback));
418 return CancelCallback();
421 CancelCallback FakeDriveService::GetChangeList(
422 int64 start_changestamp,
423 const ChangeListCallback& callback) {
424 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
425 DCHECK(!callback.is_null());
427 GetChangeListInternal(start_changestamp,
428 std::string(), // empty search query
429 std::string(), // no directory resource id,
431 default_max_results_,
432 &change_list_load_count_,
434 return CancelCallback();
437 CancelCallback FakeDriveService::GetRemainingChangeList(
438 const GURL& next_link,
439 const ChangeListCallback& callback) {
440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
441 DCHECK(!next_link.is_empty());
442 DCHECK(!callback.is_null());
444 // "changestamp", "q", "parent" and "start-offset" are parameters to
445 // implement "paging" of the result on FakeDriveService.
446 // The URL should be the one filled in GetChangeListInternal of the
447 // previous method invocation, so it should start with "http://localhost/?".
448 // See also GetChangeListInternal.
449 DCHECK_EQ(next_link.host(), "localhost");
450 DCHECK_EQ(next_link.path(), "/");
452 int64 start_changestamp = 0;
453 std::string search_query;
454 std::string directory_resource_id;
455 int start_offset = 0;
456 int max_results = default_max_results_;
457 std::vector<std::pair<std::string, std::string> > parameters;
458 if (base::SplitStringIntoKeyValuePairs(
459 next_link.query(), '=', '&', ¶meters)) {
460 for (size_t i = 0; i < parameters.size(); ++i) {
461 if (parameters[i].first == "changestamp") {
462 base::StringToInt64(parameters[i].second, &start_changestamp);
463 } else if (parameters[i].first == "q") {
465 net::UnescapeURLComponent(parameters[i].second,
466 net::UnescapeRule::URL_SPECIAL_CHARS);
467 } else if (parameters[i].first == "parent") {
468 directory_resource_id =
469 net::UnescapeURLComponent(parameters[i].second,
470 net::UnescapeRule::URL_SPECIAL_CHARS);
471 } else if (parameters[i].first == "start-offset") {
472 base::StringToInt(parameters[i].second, &start_offset);
473 } else if (parameters[i].first == "max-results") {
474 base::StringToInt(parameters[i].second, &max_results);
479 GetChangeListInternal(start_changestamp, search_query, directory_resource_id,
480 start_offset, max_results, NULL, callback);
481 return CancelCallback();
484 CancelCallback FakeDriveService::GetRemainingFileList(
485 const GURL& next_link,
486 const FileListCallback& callback) {
487 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
488 DCHECK(!next_link.is_empty());
489 DCHECK(!callback.is_null());
491 return GetRemainingChangeList(
492 next_link, base::Bind(&FileListCallbackAdapter, callback));
495 CancelCallback FakeDriveService::GetFileResource(
496 const std::string& resource_id,
497 const FileResourceCallback& callback) {
498 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
499 DCHECK(!callback.is_null());
502 base::MessageLoop::current()->PostTask(
506 base::Passed(scoped_ptr<FileResource>())));
507 return CancelCallback();
510 EntryInfo* entry = FindEntryByResourceId(resource_id);
511 if (entry && entry->change_resource.file()) {
512 base::MessageLoop::current()->PostTask(
514 base::Bind(callback, HTTP_SUCCESS, base::Passed(make_scoped_ptr(
515 new FileResource(*entry->change_resource.file())))));
516 return CancelCallback();
519 base::MessageLoop::current()->PostTask(
521 base::Bind(callback, HTTP_NOT_FOUND,
522 base::Passed(scoped_ptr<FileResource>())));
523 return CancelCallback();
526 CancelCallback FakeDriveService::GetShareUrl(
527 const std::string& resource_id,
528 const GURL& /* embed_origin */,
529 const GetShareUrlCallback& callback) {
530 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
531 DCHECK(!callback.is_null());
534 base::MessageLoop::current()->PostTask(
539 return CancelCallback();
542 EntryInfo* entry = FindEntryByResourceId(resource_id);
544 base::MessageLoop::current()->PostTask(
546 base::Bind(callback, HTTP_SUCCESS, entry->share_url));
547 return CancelCallback();
550 base::MessageLoop::current()->PostTask(
552 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
553 return CancelCallback();
556 CancelCallback FakeDriveService::GetAboutResource(
557 const AboutResourceCallback& callback) {
558 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
559 DCHECK(!callback.is_null());
562 scoped_ptr<AboutResource> null;
563 base::MessageLoop::current()->PostTask(
566 GDATA_NO_CONNECTION, base::Passed(&null)));
567 return CancelCallback();
570 ++about_resource_load_count_;
571 scoped_ptr<AboutResource> about_resource(new AboutResource(*about_resource_));
572 base::MessageLoop::current()->PostTask(
575 HTTP_SUCCESS, base::Passed(&about_resource)));
576 return CancelCallback();
579 CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) {
580 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
581 DCHECK(!callback.is_null());
582 DCHECK(app_info_value_);
585 scoped_ptr<AppList> null;
586 base::MessageLoop::current()->PostTask(
590 base::Passed(&null)));
591 return CancelCallback();
594 ++app_list_load_count_;
595 scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_));
596 base::MessageLoop::current()->PostTask(
598 base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list)));
599 return CancelCallback();
602 CancelCallback FakeDriveService::DeleteResource(
603 const std::string& resource_id,
604 const std::string& etag,
605 const EntryActionCallback& callback) {
606 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
607 DCHECK(!callback.is_null());
610 base::MessageLoop::current()->PostTask(
611 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
612 return CancelCallback();
615 EntryInfo* entry = FindEntryByResourceId(resource_id);
617 ChangeResource* change = &entry->change_resource;
618 const FileResource* file = change->file();
619 if (change->is_deleted()) {
620 base::MessageLoop::current()->PostTask(
621 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
622 return CancelCallback();
625 if (!etag.empty() && etag != file->etag()) {
626 base::MessageLoop::current()->PostTask(
627 FROM_HERE, base::Bind(callback, HTTP_PRECONDITION));
628 return CancelCallback();
631 if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) {
632 base::MessageLoop::current()->PostTask(
633 FROM_HERE, base::Bind(callback, HTTP_FORBIDDEN));
634 return CancelCallback();
637 change->set_deleted(true);
638 AddNewChangestamp(change);
639 change->set_file(scoped_ptr<FileResource>());
640 base::MessageLoop::current()->PostTask(
641 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
642 return CancelCallback();
645 base::MessageLoop::current()->PostTask(
646 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
647 return CancelCallback();
650 CancelCallback FakeDriveService::TrashResource(
651 const std::string& resource_id,
652 const EntryActionCallback& callback) {
653 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
654 DCHECK(!callback.is_null());
657 base::MessageLoop::current()->PostTask(
658 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
659 return CancelCallback();
662 EntryInfo* entry = FindEntryByResourceId(resource_id);
664 ChangeResource* change = &entry->change_resource;
665 FileResource* file = change->mutable_file();
666 GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
667 if (change->is_deleted() || file->labels().is_trashed()) {
668 error = HTTP_NOT_FOUND;
669 } else if (entry->user_permission !=
670 google_apis::drive::PERMISSION_ROLE_OWNER) {
671 error = HTTP_FORBIDDEN;
673 file->mutable_labels()->set_trashed(true);
674 AddNewChangestamp(change);
675 error = HTTP_SUCCESS;
677 base::MessageLoop::current()->PostTask(
678 FROM_HERE, base::Bind(callback, error));
679 return CancelCallback();
682 base::MessageLoop::current()->PostTask(
683 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
684 return CancelCallback();
687 CancelCallback FakeDriveService::DownloadFile(
688 const base::FilePath& local_cache_path,
689 const std::string& resource_id,
690 const DownloadActionCallback& download_action_callback,
691 const GetContentCallback& get_content_callback,
692 const ProgressCallback& progress_callback) {
693 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
694 DCHECK(!download_action_callback.is_null());
697 base::MessageLoop::current()->PostTask(
699 base::Bind(download_action_callback,
702 return CancelCallback();
705 EntryInfo* entry = FindEntryByResourceId(resource_id);
706 if (!entry || entry->change_resource.file()->IsHostedDocument()) {
707 base::MessageLoopProxy::current()->PostTask(
709 base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath()));
710 return CancelCallback();
713 const FileResource* file = entry->change_resource.file();
714 const std::string& content_data = entry->content_data;
715 int64 file_size = file->file_size();
716 DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
718 if (!get_content_callback.is_null()) {
719 const int64 kBlockSize = 5;
720 for (int64 i = 0; i < file_size; i += kBlockSize) {
721 const int64 size = std::min(kBlockSize, file_size - i);
722 scoped_ptr<std::string> content_for_callback(
723 new std::string(content_data.substr(i, size)));
724 base::MessageLoopProxy::current()->PostTask(
726 base::Bind(get_content_callback, HTTP_SUCCESS,
727 base::Passed(&content_for_callback)));
731 if (test_util::WriteStringToFile(local_cache_path, content_data)) {
732 if (!progress_callback.is_null()) {
733 // See also the comment in ResumeUpload(). For testing that clients
734 // can handle the case progress_callback is called multiple times,
735 // here we invoke the callback twice.
736 base::MessageLoopProxy::current()->PostTask(
738 base::Bind(progress_callback, file_size / 2, file_size));
739 base::MessageLoopProxy::current()->PostTask(
741 base::Bind(progress_callback, file_size, file_size));
743 base::MessageLoopProxy::current()->PostTask(
745 base::Bind(download_action_callback,
748 return CancelCallback();
751 // Failed to write the content.
752 base::MessageLoopProxy::current()->PostTask(
754 base::Bind(download_action_callback, GDATA_FILE_ERROR, base::FilePath()));
755 return CancelCallback();
758 CancelCallback FakeDriveService::CopyResource(
759 const std::string& resource_id,
760 const std::string& in_parent_resource_id,
761 const std::string& new_title,
762 const base::Time& last_modified,
763 const FileResourceCallback& callback) {
764 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
765 DCHECK(!callback.is_null());
768 base::MessageLoop::current()->PostTask(
772 base::Passed(scoped_ptr<FileResource>())));
773 return CancelCallback();
776 const std::string& parent_resource_id = in_parent_resource_id.empty() ?
777 GetRootResourceId() : in_parent_resource_id;
779 EntryInfo* entry = FindEntryByResourceId(resource_id);
781 // Make a copy and set the new resource ID and the new title.
782 scoped_ptr<EntryInfo> copied_entry(new EntryInfo);
783 copied_entry->content_data = entry->content_data;
784 copied_entry->share_url = entry->share_url;
785 copied_entry->change_resource.set_file(
786 make_scoped_ptr(new FileResource(*entry->change_resource.file())));
788 ChangeResource* new_change = &copied_entry->change_resource;
789 FileResource* new_file = new_change->mutable_file();
790 const std::string new_resource_id = GetNewResourceId();
791 new_change->set_file_id(new_resource_id);
792 new_file->set_file_id(new_resource_id);
793 new_file->set_title(new_title);
795 ParentReference parent;
796 parent.set_file_id(parent_resource_id);
797 parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
798 std::vector<ParentReference> parents;
799 parents.push_back(parent);
800 *new_file->mutable_parents() = parents;
802 if (!last_modified.is_null())
803 new_file->set_modified_date(last_modified);
805 AddNewChangestamp(new_change);
806 UpdateETag(new_file);
808 // Add the new entry to the map.
809 entries_[new_resource_id] = copied_entry.release();
811 base::MessageLoop::current()->PostTask(
815 base::Passed(make_scoped_ptr(new FileResource(*new_file)))));
816 return CancelCallback();
819 base::MessageLoop::current()->PostTask(
821 base::Bind(callback, HTTP_NOT_FOUND,
822 base::Passed(scoped_ptr<FileResource>())));
823 return CancelCallback();
826 CancelCallback FakeDriveService::UpdateResource(
827 const std::string& resource_id,
828 const std::string& parent_resource_id,
829 const std::string& new_title,
830 const base::Time& last_modified,
831 const base::Time& last_viewed_by_me,
832 const google_apis::FileResourceCallback& callback) {
833 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
834 DCHECK(!callback.is_null());
837 base::MessageLoop::current()->PostTask(
838 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION,
839 base::Passed(scoped_ptr<FileResource>())));
840 return CancelCallback();
843 EntryInfo* entry = FindEntryByResourceId(resource_id);
845 if (!UserHasWriteAccess(entry->user_permission)) {
846 base::MessageLoop::current()->PostTask(
848 base::Bind(callback, HTTP_FORBIDDEN,
849 base::Passed(scoped_ptr<FileResource>())));
850 return CancelCallback();
853 ChangeResource* change = &entry->change_resource;
854 FileResource* file = change->mutable_file();
856 if (!new_title.empty())
857 file->set_title(new_title);
859 // Set parent if necessary.
860 if (!parent_resource_id.empty()) {
861 ParentReference parent;
862 parent.set_file_id(parent_resource_id);
863 parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
865 std::vector<ParentReference> parents;
866 parents.push_back(parent);
867 *file->mutable_parents() = parents;
870 if (!last_modified.is_null())
871 file->set_modified_date(last_modified);
873 if (!last_viewed_by_me.is_null())
874 file->set_last_viewed_by_me_date(last_viewed_by_me);
876 AddNewChangestamp(change);
879 base::MessageLoop::current()->PostTask(
881 base::Bind(callback, HTTP_SUCCESS,
882 base::Passed(make_scoped_ptr(new FileResource(*file)))));
883 return CancelCallback();
886 base::MessageLoop::current()->PostTask(
888 base::Bind(callback, HTTP_NOT_FOUND,
889 base::Passed(scoped_ptr<FileResource>())));
890 return CancelCallback();
893 CancelCallback FakeDriveService::AddResourceToDirectory(
894 const std::string& parent_resource_id,
895 const std::string& resource_id,
896 const EntryActionCallback& callback) {
897 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
898 DCHECK(!callback.is_null());
901 base::MessageLoop::current()->PostTask(
902 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
903 return CancelCallback();
906 EntryInfo* entry = FindEntryByResourceId(resource_id);
908 ChangeResource* change = &entry->change_resource;
909 // On the real Drive server, resources do not necessary shape a tree
910 // structure. That is, each resource can have multiple parent.
911 // We mimic the behavior here; AddResourceToDirectoy just adds
912 // one more parent, not overwriting old ones.
913 ParentReference parent;
914 parent.set_file_id(parent_resource_id);
915 parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
916 change->mutable_file()->mutable_parents()->push_back(parent);
918 AddNewChangestamp(change);
919 base::MessageLoop::current()->PostTask(
920 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
921 return CancelCallback();
924 base::MessageLoop::current()->PostTask(
925 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
926 return CancelCallback();
929 CancelCallback FakeDriveService::RemoveResourceFromDirectory(
930 const std::string& parent_resource_id,
931 const std::string& resource_id,
932 const EntryActionCallback& callback) {
933 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
934 DCHECK(!callback.is_null());
937 base::MessageLoop::current()->PostTask(
938 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
939 return CancelCallback();
942 EntryInfo* entry = FindEntryByResourceId(resource_id);
944 ChangeResource* change = &entry->change_resource;
945 FileResource* file = change->mutable_file();
946 std::vector<ParentReference>* parents = file->mutable_parents();
947 for (size_t i = 0; i < parents->size(); ++i) {
948 if ((*parents)[i].file_id() == parent_resource_id) {
949 parents->erase(parents->begin() + i);
950 AddNewChangestamp(change);
951 base::MessageLoop::current()->PostTask(
952 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
953 return CancelCallback();
958 base::MessageLoop::current()->PostTask(
959 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
960 return CancelCallback();
963 CancelCallback FakeDriveService::AddNewDirectory(
964 const std::string& parent_resource_id,
965 const std::string& directory_title,
966 const AddNewDirectoryOptions& options,
967 const FileResourceCallback& callback) {
968 return AddNewDirectoryWithResourceId(
970 parent_resource_id.empty() ? GetRootResourceId() : parent_resource_id,
976 CancelCallback FakeDriveService::InitiateUploadNewFile(
977 const std::string& content_type,
978 int64 content_length,
979 const std::string& parent_resource_id,
980 const std::string& title,
981 const InitiateUploadNewFileOptions& options,
982 const InitiateUploadCallback& callback) {
983 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
984 DCHECK(!callback.is_null());
987 base::MessageLoop::current()->PostTask(
989 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
990 return CancelCallback();
993 if (parent_resource_id != GetRootResourceId() &&
994 !entries_.count(parent_resource_id)) {
995 base::MessageLoop::current()->PostTask(
997 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
998 return CancelCallback();
1001 GURL session_url = GetNewUploadSessionUrl();
1002 upload_sessions_[session_url] =
1003 UploadSession(content_type, content_length,
1009 base::MessageLoop::current()->PostTask(
1011 base::Bind(callback, HTTP_SUCCESS, session_url));
1012 return CancelCallback();
1015 CancelCallback FakeDriveService::InitiateUploadExistingFile(
1016 const std::string& content_type,
1017 int64 content_length,
1018 const std::string& resource_id,
1019 const InitiateUploadExistingFileOptions& options,
1020 const InitiateUploadCallback& callback) {
1021 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1022 DCHECK(!callback.is_null());
1025 base::MessageLoop::current()->PostTask(
1027 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
1028 return CancelCallback();
1031 EntryInfo* entry = FindEntryByResourceId(resource_id);
1033 base::MessageLoop::current()->PostTask(
1035 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
1036 return CancelCallback();
1039 if (!UserHasWriteAccess(entry->user_permission)) {
1040 base::MessageLoop::current()->PostTask(
1042 base::Bind(callback, HTTP_FORBIDDEN, GURL()));
1043 return CancelCallback();
1046 FileResource* file = entry->change_resource.mutable_file();
1047 if (!options.etag.empty() && options.etag != file->etag()) {
1048 base::MessageLoop::current()->PostTask(
1050 base::Bind(callback, HTTP_PRECONDITION, GURL()));
1051 return CancelCallback();
1053 // TODO(hashimoto): Update |file|'s metadata with |options|.
1055 GURL session_url = GetNewUploadSessionUrl();
1056 upload_sessions_[session_url] =
1057 UploadSession(content_type, content_length,
1058 "", // parent_resource_id
1063 base::MessageLoop::current()->PostTask(
1065 base::Bind(callback, HTTP_SUCCESS, session_url));
1066 return CancelCallback();
1069 CancelCallback FakeDriveService::GetUploadStatus(
1070 const GURL& upload_url,
1071 int64 content_length,
1072 const UploadRangeCallback& callback) {
1073 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1074 DCHECK(!callback.is_null());
1075 return CancelCallback();
1078 CancelCallback FakeDriveService::ResumeUpload(
1079 const GURL& upload_url,
1080 int64 start_position,
1082 int64 content_length,
1083 const std::string& content_type,
1084 const base::FilePath& local_file_path,
1085 const UploadRangeCallback& callback,
1086 const ProgressCallback& progress_callback) {
1087 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1088 DCHECK(!callback.is_null());
1090 FileResourceCallback completion_callback
1091 = base::Bind(&ScheduleUploadRangeCallback,
1092 callback, start_position, end_position);
1095 completion_callback.Run(GDATA_NO_CONNECTION, scoped_ptr<FileResource>());
1096 return CancelCallback();
1099 if (!upload_sessions_.count(upload_url)) {
1100 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
1101 return CancelCallback();
1104 UploadSession* session = &upload_sessions_[upload_url];
1106 // Chunks are required to be sent in such a ways that they fill from the start
1107 // of the not-yet-uploaded part with no gaps nor overlaps.
1108 if (session->uploaded_size != start_position) {
1109 completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<FileResource>());
1110 return CancelCallback();
1113 if (!progress_callback.is_null()) {
1114 // In the real GDataWapi/Drive DriveService, progress is reported in
1115 // nondeterministic timing. In this fake implementation, we choose to call
1116 // it twice per one ResumeUpload. This is for making sure that client code
1117 // works fine even if the callback is invoked more than once; it is the
1118 // crucial difference of the progress callback from others.
1119 // Note that progress is notified in the relative offset in each chunk.
1120 const int64 chunk_size = end_position - start_position;
1121 base::MessageLoop::current()->PostTask(
1122 FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size));
1123 base::MessageLoop::current()->PostTask(
1124 FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size));
1127 if (content_length != end_position) {
1128 session->uploaded_size = end_position;
1129 completion_callback.Run(HTTP_RESUME_INCOMPLETE, scoped_ptr<FileResource>());
1130 return CancelCallback();
1133 std::string content_data;
1134 if (!base::ReadFileToString(local_file_path, &content_data)) {
1135 session->uploaded_size = end_position;
1136 completion_callback.Run(GDATA_FILE_ERROR, scoped_ptr<FileResource>());
1137 return CancelCallback();
1139 session->uploaded_size = end_position;
1141 // |resource_id| is empty if the upload is for new file.
1142 if (session->resource_id.empty()) {
1143 DCHECK(!session->parent_resource_id.empty());
1144 DCHECK(!session->title.empty());
1145 const EntryInfo* new_entry = AddNewEntry(
1146 "", // auto generate resource id.
1147 session->content_type,
1149 session->parent_resource_id,
1151 false); // shared_with_me
1153 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
1154 return CancelCallback();
1157 completion_callback.Run(HTTP_CREATED, make_scoped_ptr(
1158 new FileResource(*new_entry->change_resource.file())));
1159 return CancelCallback();
1162 EntryInfo* entry = FindEntryByResourceId(session->resource_id);
1164 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
1165 return CancelCallback();
1168 ChangeResource* change = &entry->change_resource;
1169 FileResource* file = change->mutable_file();
1170 if (file->etag().empty() || session->etag != file->etag()) {
1171 completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<FileResource>());
1172 return CancelCallback();
1175 file->set_md5_checksum(base::MD5String(content_data));
1176 entry->content_data = content_data;
1177 file->set_file_size(end_position);
1178 AddNewChangestamp(change);
1181 completion_callback.Run(HTTP_SUCCESS, make_scoped_ptr(
1182 new FileResource(*file)));
1183 return CancelCallback();
1186 CancelCallback FakeDriveService::AuthorizeApp(
1187 const std::string& resource_id,
1188 const std::string& app_id,
1189 const AuthorizeAppCallback& callback) {
1190 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1191 DCHECK(!callback.is_null());
1193 if (entries_.count(resource_id) == 0) {
1194 callback.Run(google_apis::HTTP_NOT_FOUND, GURL());
1195 return CancelCallback();
1198 callback.Run(HTTP_SUCCESS,
1199 GURL(base::StringPrintf(open_url_format_.c_str(),
1200 resource_id.c_str(),
1202 return CancelCallback();
1205 CancelCallback FakeDriveService::UninstallApp(
1206 const std::string& app_id,
1207 const google_apis::EntryActionCallback& callback) {
1208 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1209 DCHECK(!callback.is_null());
1211 // Find app_id from app_info_value_ and delete.
1212 google_apis::GDataErrorCode error = google_apis::HTTP_NOT_FOUND;
1214 error = google_apis::GDATA_NO_CONNECTION;
1216 base::ListValue* items = NULL;
1217 if (app_info_value_->GetList("items", &items)) {
1218 for (size_t i = 0; i < items->GetSize(); ++i) {
1219 base::DictionaryValue* item = NULL;
1221 if (items->GetDictionary(i, &item) && item->GetString("id", &id) &&
1223 if (items->Remove(i, NULL))
1224 error = google_apis::HTTP_NO_CONTENT;
1231 base::MessageLoop::current()->PostTask(FROM_HERE,
1232 base::Bind(callback, error));
1233 return CancelCallback();
1236 void FakeDriveService::AddNewFile(const std::string& content_type,
1237 const std::string& content_data,
1238 const std::string& parent_resource_id,
1239 const std::string& title,
1240 bool shared_with_me,
1241 const FileResourceCallback& callback) {
1242 AddNewFileWithResourceId("", content_type, content_data, parent_resource_id,
1243 title, shared_with_me, callback);
1246 void FakeDriveService::AddNewFileWithResourceId(
1247 const std::string& resource_id,
1248 const std::string& content_type,
1249 const std::string& content_data,
1250 const std::string& parent_resource_id,
1251 const std::string& title,
1252 bool shared_with_me,
1253 const FileResourceCallback& callback) {
1254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1255 DCHECK(!callback.is_null());
1258 base::MessageLoop::current()->PostTask(
1260 base::Bind(callback,
1261 GDATA_NO_CONNECTION,
1262 base::Passed(scoped_ptr<FileResource>())));
1266 const EntryInfo* new_entry = AddNewEntry(resource_id,
1273 base::MessageLoop::current()->PostTask(
1275 base::Bind(callback, HTTP_NOT_FOUND,
1276 base::Passed(scoped_ptr<FileResource>())));
1280 base::MessageLoop::current()->PostTask(
1282 base::Bind(callback, HTTP_CREATED,
1283 base::Passed(make_scoped_ptr(
1284 new FileResource(*new_entry->change_resource.file())))));
1287 CancelCallback FakeDriveService::AddNewDirectoryWithResourceId(
1288 const std::string& resource_id,
1289 const std::string& parent_resource_id,
1290 const std::string& directory_title,
1291 const AddNewDirectoryOptions& options,
1292 const FileResourceCallback& callback) {
1293 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1294 DCHECK(!callback.is_null());
1297 base::MessageLoop::current()->PostTask(
1299 base::Bind(callback,
1300 GDATA_NO_CONNECTION,
1301 base::Passed(scoped_ptr<FileResource>())));
1302 return CancelCallback();
1305 const EntryInfo* new_entry = AddNewEntry(resource_id,
1306 util::kDriveFolderMimeType,
1310 false); // shared_with_me
1312 base::MessageLoop::current()->PostTask(
1314 base::Bind(callback, HTTP_NOT_FOUND,
1315 base::Passed(scoped_ptr<FileResource>())));
1316 return CancelCallback();
1319 base::MessageLoop::current()->PostTask(
1321 base::Bind(callback, HTTP_CREATED,
1322 base::Passed(make_scoped_ptr(
1323 new FileResource(*new_entry->change_resource.file())))));
1324 return CancelCallback();
1327 void FakeDriveService::SetLastModifiedTime(
1328 const std::string& resource_id,
1329 const base::Time& last_modified_time,
1330 const FileResourceCallback& callback) {
1331 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1332 DCHECK(!callback.is_null());
1335 base::MessageLoop::current()->PostTask(
1337 base::Bind(callback,
1338 GDATA_NO_CONNECTION,
1339 base::Passed(scoped_ptr<FileResource>())));
1343 EntryInfo* entry = FindEntryByResourceId(resource_id);
1345 base::MessageLoop::current()->PostTask(
1347 base::Bind(callback, HTTP_NOT_FOUND,
1348 base::Passed(scoped_ptr<FileResource>())));
1352 ChangeResource* change = &entry->change_resource;
1353 FileResource* file = change->mutable_file();
1354 file->set_modified_date(last_modified_time);
1356 base::MessageLoop::current()->PostTask(
1358 base::Bind(callback, HTTP_SUCCESS,
1359 base::Passed(make_scoped_ptr(new FileResource(*file)))));
1362 google_apis::GDataErrorCode FakeDriveService::SetUserPermission(
1363 const std::string& resource_id,
1364 google_apis::drive::PermissionRole user_permission) {
1365 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1367 EntryInfo* entry = FindEntryByResourceId(resource_id);
1369 return HTTP_NOT_FOUND;
1371 entry->user_permission = user_permission;
1372 return HTTP_SUCCESS;
1375 FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId(
1376 const std::string& resource_id) {
1377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1379 EntryInfoMap::iterator it = entries_.find(resource_id);
1380 // Deleted entries don't have FileResource.
1381 return it != entries_.end() && it->second->change_resource.file() ?
1385 std::string FakeDriveService::GetNewResourceId() {
1386 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1388 ++resource_id_count_;
1389 return base::StringPrintf("resource_id_%d", resource_id_count_);
1392 void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
1394 "etag_" + base::Int64ToString(about_resource_->largest_change_id()));
1397 void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change) {
1398 about_resource_->set_largest_change_id(
1399 about_resource_->largest_change_id() + 1);
1400 change->set_change_id(about_resource_->largest_change_id());
1403 const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry(
1404 const std::string& given_resource_id,
1405 const std::string& content_type,
1406 const std::string& content_data,
1407 const std::string& parent_resource_id,
1408 const std::string& title,
1409 bool shared_with_me) {
1410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1412 if (!parent_resource_id.empty() &&
1413 parent_resource_id != GetRootResourceId() &&
1414 !entries_.count(parent_resource_id)) {
1418 const std::string resource_id =
1419 given_resource_id.empty() ? GetNewResourceId() : given_resource_id;
1420 if (entries_.count(resource_id))
1422 GURL upload_url = GURL("https://xxx/upload/" + resource_id);
1424 scoped_ptr<EntryInfo> new_entry(new EntryInfo);
1425 ChangeResource* new_change = &new_entry->change_resource;
1426 FileResource* new_file = new FileResource;
1427 new_change->set_file(make_scoped_ptr(new_file));
1429 // Set the resource ID and the title
1430 new_change->set_file_id(resource_id);
1431 new_file->set_file_id(resource_id);
1432 new_file->set_title(title);
1433 // Set the contents, size and MD5 for a file.
1434 if (content_type != util::kDriveFolderMimeType &&
1435 !util::IsKnownHostedDocumentMimeType(content_type)) {
1436 new_entry->content_data = content_data;
1437 new_file->set_file_size(content_data.size());
1438 new_file->set_md5_checksum(base::MD5String(content_data));
1441 if (shared_with_me) {
1442 // Set current time to mark the file as shared_with_me.
1443 new_file->set_shared_with_me_date(base::Time::Now());
1446 std::string escaped_resource_id = net::EscapePath(resource_id);
1449 new_file->set_mime_type(content_type);
1451 // Set alternate link if needed.
1452 if (content_type == util::kGoogleDocumentMimeType)
1453 new_file->set_alternate_link(GURL("https://document_alternate_link"));
1456 if (!parent_resource_id.empty()) {
1457 ParentReference parent;
1458 parent.set_file_id(parent_resource_id);
1459 parent.set_parent_link(GetFakeLinkUrl(parent.file_id()));
1460 std::vector<ParentReference> parents;
1461 parents.push_back(parent);
1462 *new_file->mutable_parents() = parents;
1465 new_entry->share_url = net::AppendOrReplaceQueryParameter(
1466 share_url_base_, "name", title);
1468 AddNewChangestamp(new_change);
1469 UpdateETag(new_file);
1471 base::Time published_date =
1472 base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_);
1473 new_file->set_created_date(published_date);
1475 EntryInfo* raw_new_entry = new_entry.release();
1476 entries_[resource_id] = raw_new_entry;
1477 return raw_new_entry;
1480 void FakeDriveService::GetChangeListInternal(
1481 int64 start_changestamp,
1482 const std::string& search_query,
1483 const std::string& directory_resource_id,
1487 const ChangeListCallback& callback) {
1489 base::MessageLoop::current()->PostTask(
1491 base::Bind(callback,
1492 GDATA_NO_CONNECTION,
1493 base::Passed(scoped_ptr<ChangeList>())));
1497 // Filter out entries per parameters like |directory_resource_id| and
1499 ScopedVector<ChangeResource> entries;
1500 int num_entries_matched = 0;
1501 for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end();
1503 const ChangeResource& entry = it->second->change_resource;
1504 bool should_exclude = false;
1506 // If |directory_resource_id| is set, exclude the entry if it's not in
1507 // the target directory.
1508 if (!directory_resource_id.empty()) {
1509 // Get the parent resource ID of the entry.
1510 std::string parent_resource_id;
1511 if (entry.file() && !entry.file()->parents().empty())
1512 parent_resource_id = entry.file()->parents()[0].file_id();
1514 if (directory_resource_id != parent_resource_id)
1515 should_exclude = true;
1518 // If |search_query| is set, exclude the entry if it does not contain the
1519 // search query in the title.
1520 if (!should_exclude && !search_query.empty() &&
1521 !EntryMatchWithQuery(entry, search_query)) {
1522 should_exclude = true;
1525 // If |start_changestamp| is set, exclude the entry if the
1526 // changestamp is older than |largest_changestamp|.
1527 // See https://developers.google.com/google-apps/documents-list/
1528 // #retrieving_all_changes_since_a_given_changestamp
1529 if (start_changestamp > 0 && entry.change_id() < start_changestamp)
1530 should_exclude = true;
1532 // If the caller requests other list than change list by specifying
1533 // zero-|start_changestamp|, exclude deleted entry from the result.
1534 const bool deleted = entry.is_deleted() ||
1535 (entry.file() && entry.file()->labels().is_trashed());
1536 if (!start_changestamp && deleted)
1537 should_exclude = true;
1539 // The entry matched the criteria for inclusion.
1540 if (!should_exclude)
1541 ++num_entries_matched;
1543 // If |start_offset| is set, exclude the entry if the entry is before the
1544 // start index. <= instead of < as |num_entries_matched| was
1545 // already incremented.
1546 if (start_offset > 0 && num_entries_matched <= start_offset)
1547 should_exclude = true;
1549 if (!should_exclude) {
1550 scoped_ptr<ChangeResource> entry_copied(new ChangeResource);
1551 entry_copied->set_change_id(entry.change_id());
1552 entry_copied->set_file_id(entry.file_id());
1553 entry_copied->set_deleted(entry.is_deleted());
1555 entry_copied->set_file(
1556 make_scoped_ptr(new FileResource(*entry.file())));
1558 entry_copied->set_modification_date(entry.modification_date());
1559 entries.push_back(entry_copied.release());
1563 scoped_ptr<ChangeList> change_list(new ChangeList);
1564 if (start_changestamp > 0 && start_offset == 0) {
1565 change_list->set_largest_change_id(about_resource_->largest_change_id());
1568 // If |max_results| is set, trim the entries if the number exceeded the max
1570 if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) {
1571 entries.erase(entries.begin() + max_results, entries.end());
1572 // Adds the next URL.
1573 // Here, we embed information which is needed for continuing the
1574 // GetChangeList request in the next invocation into url query
1576 GURL next_url(base::StringPrintf(
1577 "http://localhost/?start-offset=%d&max-results=%d",
1578 start_offset + max_results,
1580 if (start_changestamp > 0) {
1581 next_url = net::AppendOrReplaceQueryParameter(
1582 next_url, "changestamp",
1583 base::Int64ToString(start_changestamp).c_str());
1585 if (!search_query.empty()) {
1586 next_url = net::AppendOrReplaceQueryParameter(
1587 next_url, "q", search_query);
1589 if (!directory_resource_id.empty()) {
1590 next_url = net::AppendOrReplaceQueryParameter(
1591 next_url, "parent", directory_resource_id);
1594 change_list->set_next_link(next_url);
1596 *change_list->mutable_items() = entries.Pass();
1600 base::MessageLoop::current()->PostTask(
1602 base::Bind(callback, HTTP_SUCCESS, base::Passed(&change_list)));
1605 GURL FakeDriveService::GetNewUploadSessionUrl() {
1606 return GURL("https://upload_session_url/" +
1607 base::Int64ToString(next_upload_sequence_number_++));
1610 google_apis::CancelCallback FakeDriveService::AddPermission(
1611 const std::string& resource_id,
1612 const std::string& email,
1613 google_apis::drive::PermissionRole role,
1614 const google_apis::EntryActionCallback& callback) {
1615 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1616 DCHECK(!callback.is_null());
1619 return CancelCallback();
1622 } // namespace drive