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/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_tokenizer.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "chrome/browser/drive/drive_api_util.h"
20 #include "chrome/browser/google_apis/drive_api_parser.h"
21 #include "chrome/browser/google_apis/gdata_wapi_parser.h"
22 #include "chrome/browser/google_apis/test_util.h"
23 #include "chrome/browser/google_apis/time_util.h"
24 #include "content/public/browser/browser_thread.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::AccountMetadata;
32 using google_apis::AppList;
33 using google_apis::AppListCallback;
34 using google_apis::AuthStatusCallback;
35 using google_apis::AuthorizeAppCallback;
36 using google_apis::CancelCallback;
37 using google_apis::DownloadActionCallback;
38 using google_apis::EntryActionCallback;
39 using google_apis::GDATA_FILE_ERROR;
40 using google_apis::GDATA_NO_CONNECTION;
41 using google_apis::GDATA_OTHER_ERROR;
42 using google_apis::GDataErrorCode;
43 using google_apis::GetContentCallback;
44 using google_apis::GetResourceEntryCallback;
45 using google_apis::GetResourceListCallback;
46 using google_apis::GetShareUrlCallback;
47 using google_apis::HTTP_BAD_REQUEST;
48 using google_apis::HTTP_CREATED;
49 using google_apis::HTTP_NOT_FOUND;
50 using google_apis::HTTP_PRECONDITION;
51 using google_apis::HTTP_RESUME_INCOMPLETE;
52 using google_apis::HTTP_SUCCESS;
53 using google_apis::InitiateUploadCallback;
54 using google_apis::Link;
55 using google_apis::ProgressCallback;
56 using google_apis::ResourceEntry;
57 using google_apis::ResourceList;
58 using google_apis::UploadRangeCallback;
59 using google_apis::UploadRangeResponse;
60 namespace test_util = google_apis::test_util;
65 // Rel property of an upload link in the entries dictionary value.
66 const char kUploadUrlRel[] =
67 "http://schemas.google.com/g/2005#resumable-create-media";
69 // Rel property of a share link in the entries dictionary value.
70 const char kShareUrlRel[] =
71 "http://schemas.google.com/docs/2007#share";
73 // Returns true if a resource entry matches with the search query.
74 // Supports queries consist of following format.
75 // - Phrases quoted by double/single quotes
76 // - AND search for multiple words/phrases segmented by space
77 // - Limited attribute search. Only "title:" is supported.
78 bool EntryMatchWithQuery(const ResourceEntry& entry,
79 const std::string& query) {
80 base::StringTokenizer tokenizer(query, " ");
81 tokenizer.set_quote_chars("\"'");
82 while (tokenizer.GetNext()) {
83 std::string key, value;
84 const std::string& token = tokenizer.token();
85 if (token.find(':') == std::string::npos) {
86 TrimString(token, "\"'", &value);
88 base::StringTokenizer key_value(token, ":");
89 key_value.set_quote_chars("\"'");
90 if (!key_value.GetNext())
92 key = key_value.token();
93 if (!key_value.GetNext())
95 TrimString(key_value.token(), "\"'", &value);
98 // TODO(peria): Deal with other attributes than title.
99 if (!key.empty() && key != "title")
101 // Search query in the title.
102 if (entry.title().find(value) == std::string::npos)
108 // Returns |url| without query parameter.
109 GURL RemoveQueryParameter(const GURL& url) {
110 GURL::Replacements replacements;
111 replacements.ClearQuery();
112 return url.ReplaceComponents(replacements);
115 void ScheduleUploadRangeCallback(const UploadRangeCallback& callback,
116 int64 start_position,
118 GDataErrorCode error,
119 scoped_ptr<ResourceEntry> entry) {
120 base::MessageLoop::current()->PostTask(
123 UploadRangeResponse(error,
126 base::Passed(&entry)));
129 void EntryActionCallbackAdapter(
130 const EntryActionCallback& callback,
131 GDataErrorCode error, scoped_ptr<ResourceEntry> resource_entry) {
137 struct FakeDriveService::UploadSession {
138 std::string content_type;
139 int64 content_length;
140 std::string parent_resource_id;
141 std::string resource_id;
152 std::string content_type,
153 int64 content_length,
154 std::string parent_resource_id,
155 std::string resource_id,
158 : content_type(content_type),
159 content_length(content_length),
160 parent_resource_id(parent_resource_id),
161 resource_id(resource_id),
168 FakeDriveService::FakeDriveService()
169 : largest_changestamp_(0),
170 published_date_seq_(0),
171 next_upload_sequence_number_(0),
172 default_max_results_(0),
173 resource_id_count_(0),
174 resource_list_load_count_(0),
175 change_list_load_count_(0),
176 directory_load_count_(0),
177 about_resource_load_count_(0),
178 app_list_load_count_(0),
179 blocked_resource_list_load_count_(0),
181 never_return_all_resource_list_(false) {
182 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
185 FakeDriveService::~FakeDriveService() {
186 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
189 bool FakeDriveService::LoadResourceListForWapi(
190 const std::string& relative_path) {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192 scoped_ptr<Value> raw_value = test_util::LoadJSONFile(relative_path);
193 base::DictionaryValue* as_dict = NULL;
194 scoped_ptr<base::Value> feed;
195 base::DictionaryValue* feed_as_dict = NULL;
197 // Extract the "feed" from the raw value and take the ownership.
198 // Note that Remove() transfers the ownership to |feed|.
199 if (raw_value->GetAsDictionary(&as_dict) &&
200 as_dict->Remove("feed", &feed) &&
201 feed->GetAsDictionary(&feed_as_dict)) {
202 ignore_result(feed.release());
203 resource_list_value_.reset(feed_as_dict);
205 // Go through entries and convert test$data from a string to a binary blob.
206 base::ListValue* entries = NULL;
207 if (feed_as_dict->GetList("entry", &entries)) {
208 for (size_t i = 0; i < entries->GetSize(); ++i) {
209 base::DictionaryValue* entry = NULL;
210 std::string content_data;
211 if (entries->GetDictionary(i, &entry) &&
212 entry->GetString("test$data", &content_data)) {
213 entry->Set("test$data",
214 base::BinaryValue::CreateWithCopiedBuffer(
215 content_data.c_str(), content_data.size()));
221 return resource_list_value_;
224 bool FakeDriveService::LoadAccountMetadataForWapi(
225 const std::string& relative_path) {
226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
228 // Load JSON data, which must be a dictionary.
229 scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path);
230 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
231 account_metadata_value_.reset(
232 static_cast<base::DictionaryValue*>(value.release()));
234 // Update the largest_changestamp_.
235 scoped_ptr<AccountMetadata> account_metadata =
236 AccountMetadata::CreateFrom(*account_metadata_value_);
237 largest_changestamp_ = account_metadata->largest_changestamp();
239 // Add the largest changestamp to the existing entries.
240 // This will be used to generate change lists in GetResourceList().
241 if (resource_list_value_) {
242 base::ListValue* entries = NULL;
243 if (resource_list_value_->GetList("entry", &entries)) {
244 for (size_t i = 0; i < entries->GetSize(); ++i) {
245 base::DictionaryValue* entry = NULL;
246 if (entries->GetDictionary(i, &entry)) {
247 entry->SetString("docs$changestamp.value",
248 base::Int64ToString(largest_changestamp_));
254 return account_metadata_value_;
257 bool FakeDriveService::LoadAppListForDriveApi(
258 const std::string& relative_path) {
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
260 app_info_value_ = test_util::LoadJSONFile(relative_path);
261 return app_info_value_;
264 void FakeDriveService::SetQuotaValue(int64 used, int64 total) {
265 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
266 DCHECK(account_metadata_value_);
268 account_metadata_value_->SetString("entry.gd$quotaBytesUsed.$t",
269 base::Int64ToString16(used));
270 account_metadata_value_->SetString("entry.gd$quotaBytesTotal.$t",
271 base::Int64ToString16(total));
274 GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
275 return GURL("https://fake_server/" + net::EscapePath(resource_id));
278 void FakeDriveService::Initialize(const std::string& account_id) {
279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
282 void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
283 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
286 void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
290 bool FakeDriveService::CanSendRequest() const {
291 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
295 ResourceIdCanonicalizer FakeDriveService::GetResourceIdCanonicalizer() const {
296 return util::GetIdentityResourceIdCanonicalizer();
299 bool FakeDriveService::HasAccessToken() const {
300 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
304 void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 DCHECK(!callback.is_null());
307 callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
310 bool FakeDriveService::HasRefreshToken() const {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
315 void FakeDriveService::ClearAccessToken() {
316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
319 void FakeDriveService::ClearRefreshToken() {
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
323 std::string FakeDriveService::GetRootResourceId() const {
327 CancelCallback FakeDriveService::GetAllResourceList(
328 const GetResourceListCallback& callback) {
329 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
330 DCHECK(!callback.is_null());
332 if (never_return_all_resource_list_) {
333 ++blocked_resource_list_load_count_;
334 return CancelCallback();
337 GetResourceListInternal(0, // start changestamp
338 std::string(), // empty search query
339 std::string(), // no directory resource id,
341 default_max_results_,
342 &resource_list_load_count_,
344 return CancelCallback();
347 CancelCallback FakeDriveService::GetResourceListInDirectory(
348 const std::string& directory_resource_id,
349 const GetResourceListCallback& callback) {
350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
351 DCHECK(!directory_resource_id.empty());
352 DCHECK(!callback.is_null());
354 GetResourceListInternal(0, // start changestamp
355 std::string(), // empty search query
356 directory_resource_id,
358 default_max_results_,
359 &directory_load_count_,
361 return CancelCallback();
364 CancelCallback FakeDriveService::Search(
365 const std::string& search_query,
366 const GetResourceListCallback& callback) {
367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
368 DCHECK(!search_query.empty());
369 DCHECK(!callback.is_null());
371 GetResourceListInternal(0, // start changestamp
373 std::string(), // no directory resource id,
375 default_max_results_,
378 return CancelCallback();
381 CancelCallback FakeDriveService::SearchByTitle(
382 const std::string& title,
383 const std::string& directory_resource_id,
384 const GetResourceListCallback& callback) {
385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
386 DCHECK(!title.empty());
387 DCHECK(!callback.is_null());
389 // Note: the search implementation here doesn't support quotation unescape,
390 // so don't escape here.
391 GetResourceListInternal(0, // start changestamp
392 base::StringPrintf("title:'%s'", title.c_str()),
393 directory_resource_id,
395 default_max_results_,
398 return CancelCallback();
401 CancelCallback FakeDriveService::GetChangeList(
402 int64 start_changestamp,
403 const GetResourceListCallback& callback) {
404 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
405 DCHECK(!callback.is_null());
407 GetResourceListInternal(start_changestamp,
408 std::string(), // empty search query
409 std::string(), // no directory resource id,
411 default_max_results_,
412 &change_list_load_count_,
414 return CancelCallback();
417 CancelCallback FakeDriveService::GetRemainingChangeList(
418 const GURL& next_link,
419 const GetResourceListCallback& callback) {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
421 DCHECK(!next_link.is_empty());
422 DCHECK(!callback.is_null());
424 return GetRemainingResourceList(next_link, callback);
427 CancelCallback FakeDriveService::GetRemainingFileList(
428 const GURL& next_link,
429 const GetResourceListCallback& callback) {
430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
431 DCHECK(!next_link.is_empty());
432 DCHECK(!callback.is_null());
434 return GetRemainingResourceList(next_link, callback);
437 CancelCallback FakeDriveService::GetResourceEntry(
438 const std::string& resource_id,
439 const GetResourceEntryCallback& callback) {
440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
441 DCHECK(!callback.is_null());
444 scoped_ptr<ResourceEntry> null;
445 base::MessageLoop::current()->PostTask(
449 base::Passed(&null)));
450 return CancelCallback();
453 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
455 scoped_ptr<ResourceEntry> resource_entry =
456 ResourceEntry::CreateFrom(*entry);
457 base::MessageLoop::current()->PostTask(
459 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
460 return CancelCallback();
463 scoped_ptr<ResourceEntry> null;
464 base::MessageLoop::current()->PostTask(
466 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
467 return CancelCallback();
470 CancelCallback FakeDriveService::GetShareUrl(
471 const std::string& resource_id,
472 const GURL& /* embed_origin */,
473 const GetShareUrlCallback& callback) {
474 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
475 DCHECK(!callback.is_null());
478 scoped_ptr<ResourceEntry> null;
479 base::MessageLoop::current()->PostTask(
484 return CancelCallback();
487 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
489 // Share urls are stored in the resource entry, and they do not rely on the
491 scoped_ptr<ResourceEntry> resource_entry =
492 ResourceEntry::CreateFrom(*entry);
493 const Link* share_url = resource_entry->GetLinkByType(Link::LINK_SHARE);
495 base::MessageLoop::current()->PostTask(
497 base::Bind(callback, HTTP_SUCCESS, share_url->href()));
499 base::MessageLoop::current()->PostTask(
501 base::Bind(callback, HTTP_SUCCESS, GURL()));
503 return CancelCallback();
506 scoped_ptr<ResourceEntry> null;
507 base::MessageLoop::current()->PostTask(
509 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
510 return CancelCallback();
513 CancelCallback FakeDriveService::GetAboutResource(
514 const AboutResourceCallback& callback) {
515 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
516 DCHECK(!callback.is_null());
519 scoped_ptr<AboutResource> null;
520 base::MessageLoop::current()->PostTask(
523 GDATA_NO_CONNECTION, base::Passed(&null)));
524 return CancelCallback();
527 ++about_resource_load_count_;
528 scoped_ptr<AboutResource> about_resource(
529 util::ConvertAccountMetadataToAboutResource(
530 *AccountMetadata::CreateFrom(*account_metadata_value_),
531 GetRootResourceId()));
532 // Overwrite the change id.
533 about_resource->set_largest_change_id(largest_changestamp_);
534 base::MessageLoop::current()->PostTask(
537 HTTP_SUCCESS, base::Passed(&about_resource)));
538 return CancelCallback();
541 CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) {
542 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
543 DCHECK(!callback.is_null());
544 DCHECK(app_info_value_);
547 scoped_ptr<AppList> null;
548 base::MessageLoop::current()->PostTask(
552 base::Passed(&null)));
553 return CancelCallback();
556 ++app_list_load_count_;
557 scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_));
558 base::MessageLoop::current()->PostTask(
560 base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list)));
561 return CancelCallback();
564 CancelCallback FakeDriveService::DeleteResource(
565 const std::string& resource_id,
566 const std::string& etag,
567 const EntryActionCallback& callback) {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
569 DCHECK(!callback.is_null());
572 base::MessageLoop::current()->PostTask(
573 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
574 return CancelCallback();
577 base::ListValue* entries = NULL;
578 // Go through entries and remove the one that matches |resource_id|.
579 if (resource_list_value_->GetList("entry", &entries)) {
580 for (size_t i = 0; i < entries->GetSize(); ++i) {
581 base::DictionaryValue* entry = NULL;
582 std::string current_resource_id;
583 if (entries->GetDictionary(i, &entry) &&
584 entry->GetString("gd$resourceId.$t", ¤t_resource_id) &&
585 resource_id == current_resource_id) {
586 GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
587 if (entry->HasKey("gd$deleted")) {
588 error = HTTP_NOT_FOUND;
590 entry->Set("gd$deleted", new DictionaryValue);
591 AddNewChangestampAndETag(entry);
592 error = HTTP_SUCCESS;
594 base::MessageLoop::current()->PostTask(
595 FROM_HERE, base::Bind(callback, error));
596 return CancelCallback();
601 base::MessageLoop::current()->PostTask(
602 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
603 return CancelCallback();
606 CancelCallback FakeDriveService::DownloadFile(
607 const base::FilePath& local_cache_path,
608 const std::string& resource_id,
609 const DownloadActionCallback& download_action_callback,
610 const GetContentCallback& get_content_callback,
611 const ProgressCallback& progress_callback) {
612 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
613 DCHECK(!download_action_callback.is_null());
616 base::MessageLoop::current()->PostTask(
618 base::Bind(download_action_callback,
621 return CancelCallback();
624 // The field content.src is the URL to download the file.
625 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
627 base::MessageLoopProxy::current()->PostTask(
629 base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath()));
630 return CancelCallback();
633 // Write "x"s of the file size specified in the entry.
634 std::string file_size_string;
635 entry->GetString("docs$size.$t", &file_size_string);
637 if (base::StringToInt64(file_size_string, &file_size)) {
638 base::BinaryValue* content_binary_data;
639 std::string content_data;
640 if (entry->GetBinary("test$data", &content_binary_data)) {
641 content_data = std::string(content_binary_data->GetBuffer(),
642 content_binary_data->GetSize());
644 DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
646 if (!get_content_callback.is_null()) {
647 const int64 kBlockSize = 5;
648 for (int64 i = 0; i < file_size; i += kBlockSize) {
649 const int64 size = std::min(kBlockSize, file_size - i);
650 scoped_ptr<std::string> content_for_callback(
651 new std::string(content_data.substr(i, size)));
652 base::MessageLoopProxy::current()->PostTask(
654 base::Bind(get_content_callback, HTTP_SUCCESS,
655 base::Passed(&content_for_callback)));
659 if (test_util::WriteStringToFile(local_cache_path, content_data)) {
660 if (!progress_callback.is_null()) {
661 // See also the comment in ResumeUpload(). For testing that clients
662 // can handle the case progress_callback is called multiple times,
663 // here we invoke the callback twice.
664 base::MessageLoopProxy::current()->PostTask(
666 base::Bind(progress_callback, file_size / 2, file_size));
667 base::MessageLoopProxy::current()->PostTask(
669 base::Bind(progress_callback, file_size, file_size));
671 base::MessageLoopProxy::current()->PostTask(
673 base::Bind(download_action_callback,
676 return CancelCallback();
680 // Failed to write the content.
681 base::MessageLoopProxy::current()->PostTask(
683 base::Bind(download_action_callback, GDATA_FILE_ERROR, base::FilePath()));
684 return CancelCallback();
687 CancelCallback FakeDriveService::CopyResource(
688 const std::string& resource_id,
689 const std::string& in_parent_resource_id,
690 const std::string& new_title,
691 const base::Time& last_modified,
692 const GetResourceEntryCallback& callback) {
693 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
694 DCHECK(!callback.is_null());
697 scoped_ptr<ResourceEntry> null;
698 base::MessageLoop::current()->PostTask(
702 base::Passed(&null)));
703 return CancelCallback();
706 const std::string& parent_resource_id = in_parent_resource_id.empty() ?
707 GetRootResourceId() : in_parent_resource_id;
709 base::ListValue* entries = NULL;
710 // Go through entries and copy the one that matches |resource_id|.
711 if (resource_list_value_->GetList("entry", &entries)) {
712 for (size_t i = 0; i < entries->GetSize(); ++i) {
713 base::DictionaryValue* entry = NULL;
714 std::string current_resource_id;
715 if (entries->GetDictionary(i, &entry) &&
716 entry->GetString("gd$resourceId.$t", ¤t_resource_id) &&
717 resource_id == current_resource_id) {
718 // Make a copy and set the new resource ID and the new title.
719 scoped_ptr<DictionaryValue> copied_entry(entry->DeepCopy());
720 copied_entry->SetString("gd$resourceId.$t",
721 resource_id + "_copied");
722 copied_entry->SetString("title.$t", new_title);
724 // Reset parent directory.
725 base::ListValue* links = NULL;
726 if (!copied_entry->GetList("link", &links)) {
727 links = new base::ListValue;
728 copied_entry->Set("link", links);
732 base::DictionaryValue* link = new base::DictionaryValue;
734 "rel", "http://schemas.google.com/docs/2007#parent");
735 link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec());
738 if (!last_modified.is_null()) {
739 copied_entry->SetString(
741 google_apis::util::FormatTimeAsString(last_modified));
744 AddNewChangestampAndETag(copied_entry.get());
746 // Parse the new entry.
747 scoped_ptr<ResourceEntry> resource_entry =
748 ResourceEntry::CreateFrom(*copied_entry);
749 // Add it to the resource list.
750 entries->Append(copied_entry.release());
752 base::MessageLoop::current()->PostTask(
756 base::Passed(&resource_entry)));
757 return CancelCallback();
762 scoped_ptr<ResourceEntry> null;
763 base::MessageLoop::current()->PostTask(
765 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
766 return CancelCallback();
769 CancelCallback FakeDriveService::CopyHostedDocument(
770 const std::string& resource_id,
771 const std::string& new_title,
772 const GetResourceEntryCallback& callback) {
773 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
774 DCHECK(!callback.is_null());
777 resource_id, std::string(), new_title, base::Time(), callback);
780 CancelCallback FakeDriveService::MoveResource(
781 const std::string& resource_id,
782 const std::string& parent_resource_id,
783 const std::string& new_title,
784 const base::Time& last_modified,
785 const google_apis::GetResourceEntryCallback& callback) {
786 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
787 DCHECK(!callback.is_null());
790 base::MessageLoop::current()->PostTask(
791 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION,
792 base::Passed(scoped_ptr<ResourceEntry>())));
793 return CancelCallback();
796 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
798 entry->SetString("title.$t", new_title);
800 // Set parent if necessary.
801 if (!parent_resource_id.empty()) {
802 base::ListValue* links = NULL;
803 if (!entry->GetList("link", &links)) {
804 links = new base::ListValue;
805 entry->Set("link", links);
808 // Remove old parent(s).
809 for (size_t i = 0; i < links->GetSize(); ) {
810 base::DictionaryValue* link = NULL;
813 if (links->GetDictionary(i, &link) &&
814 link->GetString("rel", &rel) &&
815 link->GetString("href", &href) &&
816 rel == "http://schemas.google.com/docs/2007#parent") {
817 links->Remove(i, NULL);
823 base::DictionaryValue* link = new base::DictionaryValue;
824 link->SetString("rel", "http://schemas.google.com/docs/2007#parent");
826 "href", GetFakeLinkUrl(parent_resource_id).spec());
830 if (!last_modified.is_null()) {
833 google_apis::util::FormatTimeAsString(last_modified));
836 AddNewChangestampAndETag(entry);
838 // Parse the new entry.
839 scoped_ptr<ResourceEntry> resource_entry =
840 ResourceEntry::CreateFrom(*entry);
841 base::MessageLoop::current()->PostTask(
843 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
844 return CancelCallback();
847 base::MessageLoop::current()->PostTask(
849 base::Bind(callback, HTTP_NOT_FOUND,
850 base::Passed(scoped_ptr<ResourceEntry>())));
851 return CancelCallback();
854 CancelCallback FakeDriveService::RenameResource(
855 const std::string& resource_id,
856 const std::string& new_title,
857 const EntryActionCallback& callback) {
858 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
859 DCHECK(!callback.is_null());
862 resource_id, std::string(), new_title, base::Time(),
863 base::Bind(&EntryActionCallbackAdapter, callback));
866 CancelCallback FakeDriveService::TouchResource(
867 const std::string& resource_id,
868 const base::Time& modified_date,
869 const base::Time& last_viewed_by_me_date,
870 const GetResourceEntryCallback& callback) {
871 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
872 DCHECK(!modified_date.is_null());
873 DCHECK(!last_viewed_by_me_date.is_null());
874 DCHECK(!callback.is_null());
877 base::MessageLoop::current()->PostTask(
879 base::Bind(callback, GDATA_NO_CONNECTION,
880 base::Passed(scoped_ptr<ResourceEntry>())));
881 return CancelCallback();
884 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
886 base::MessageLoop::current()->PostTask(
888 base::Bind(callback, HTTP_NOT_FOUND,
889 base::Passed(scoped_ptr<ResourceEntry>())));
890 return CancelCallback();
893 entry->SetString("updated.$t",
894 google_apis::util::FormatTimeAsString(modified_date));
897 google_apis::util::FormatTimeAsString(last_viewed_by_me_date));
898 AddNewChangestampAndETag(entry);
900 scoped_ptr<ResourceEntry> parsed_entry(ResourceEntry::CreateFrom(*entry));
901 base::MessageLoop::current()->PostTask(
903 base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry)));
904 return CancelCallback();
907 CancelCallback FakeDriveService::AddResourceToDirectory(
908 const std::string& parent_resource_id,
909 const std::string& resource_id,
910 const EntryActionCallback& callback) {
911 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
912 DCHECK(!callback.is_null());
915 base::MessageLoop::current()->PostTask(
916 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
917 return CancelCallback();
920 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
922 base::ListValue* links = NULL;
923 if (!entry->GetList("link", &links)) {
924 links = new base::ListValue;
925 entry->Set("link", links);
928 // On the real Drive server, resources do not necessary shape a tree
929 // structure. That is, each resource can have multiple parent.
930 // We mimic the behavior here; AddResourceToDirectoy just adds
931 // one more parent link, not overwriting old links.
932 base::DictionaryValue* link = new base::DictionaryValue;
933 link->SetString("rel", "http://schemas.google.com/docs/2007#parent");
935 "href", GetFakeLinkUrl(parent_resource_id).spec());
938 AddNewChangestampAndETag(entry);
939 base::MessageLoop::current()->PostTask(
940 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
941 return CancelCallback();
944 base::MessageLoop::current()->PostTask(
945 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
946 return CancelCallback();
949 CancelCallback FakeDriveService::RemoveResourceFromDirectory(
950 const std::string& parent_resource_id,
951 const std::string& resource_id,
952 const EntryActionCallback& callback) {
953 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
954 DCHECK(!callback.is_null());
957 base::MessageLoop::current()->PostTask(
958 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
959 return CancelCallback();
962 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
964 base::ListValue* links = NULL;
965 if (entry->GetList("link", &links)) {
966 GURL parent_content_url = GetFakeLinkUrl(parent_resource_id);
967 for (size_t i = 0; i < links->GetSize(); ++i) {
968 base::DictionaryValue* link = NULL;
971 if (links->GetDictionary(i, &link) &&
972 link->GetString("rel", &rel) &&
973 link->GetString("href", &href) &&
974 rel == "http://schemas.google.com/docs/2007#parent" &&
975 GURL(href) == parent_content_url) {
976 links->Remove(i, NULL);
977 AddNewChangestampAndETag(entry);
978 base::MessageLoop::current()->PostTask(
979 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
980 return CancelCallback();
986 base::MessageLoop::current()->PostTask(
987 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
988 return CancelCallback();
991 CancelCallback FakeDriveService::AddNewDirectory(
992 const std::string& parent_resource_id,
993 const std::string& directory_title,
994 const GetResourceEntryCallback& callback) {
995 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
996 DCHECK(!callback.is_null());
999 scoped_ptr<ResourceEntry> null;
1000 base::MessageLoop::current()->PostTask(
1002 base::Bind(callback,
1003 GDATA_NO_CONNECTION,
1004 base::Passed(&null)));
1005 return CancelCallback();
1008 const char kContentType[] = "application/atom+xml;type=feed";
1009 const base::DictionaryValue* new_entry = AddNewEntry(kContentType,
1013 false, // shared_with_me
1016 scoped_ptr<ResourceEntry> null;
1017 base::MessageLoop::current()->PostTask(
1019 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1020 return CancelCallback();
1023 scoped_ptr<ResourceEntry> parsed_entry(ResourceEntry::CreateFrom(*new_entry));
1024 base::MessageLoop::current()->PostTask(
1026 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
1027 return CancelCallback();
1030 CancelCallback FakeDriveService::InitiateUploadNewFile(
1031 const std::string& content_type,
1032 int64 content_length,
1033 const std::string& parent_resource_id,
1034 const std::string& title,
1035 const InitiateUploadCallback& callback) {
1036 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1037 DCHECK(!callback.is_null());
1040 base::MessageLoop::current()->PostTask(
1042 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
1043 return CancelCallback();
1046 if (parent_resource_id != GetRootResourceId() &&
1047 !FindEntryByResourceId(parent_resource_id)) {
1048 base::MessageLoop::current()->PostTask(
1050 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
1051 return CancelCallback();
1054 GURL session_url = GetNewUploadSessionUrl();
1055 upload_sessions_[session_url] =
1056 UploadSession(content_type, content_length,
1062 base::MessageLoop::current()->PostTask(
1064 base::Bind(callback, HTTP_SUCCESS, session_url));
1065 return CancelCallback();
1068 CancelCallback FakeDriveService::InitiateUploadExistingFile(
1069 const std::string& content_type,
1070 int64 content_length,
1071 const std::string& resource_id,
1072 const std::string& etag,
1073 const InitiateUploadCallback& callback) {
1074 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1075 DCHECK(!callback.is_null());
1078 base::MessageLoop::current()->PostTask(
1080 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
1081 return CancelCallback();
1084 DictionaryValue* entry = FindEntryByResourceId(resource_id);
1086 base::MessageLoop::current()->PostTask(
1088 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
1089 return CancelCallback();
1092 std::string entry_etag;
1093 entry->GetString("gd$etag", &entry_etag);
1094 if (!etag.empty() && etag != entry_etag) {
1095 base::MessageLoop::current()->PostTask(
1097 base::Bind(callback, HTTP_PRECONDITION, GURL()));
1098 return CancelCallback();
1101 GURL session_url = GetNewUploadSessionUrl();
1102 upload_sessions_[session_url] =
1103 UploadSession(content_type, content_length,
1104 "", // parent_resource_id
1109 base::MessageLoop::current()->PostTask(
1111 base::Bind(callback, HTTP_SUCCESS, session_url));
1112 return CancelCallback();
1115 CancelCallback FakeDriveService::GetUploadStatus(
1116 const GURL& upload_url,
1117 int64 content_length,
1118 const UploadRangeCallback& callback) {
1119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1120 DCHECK(!callback.is_null());
1121 return CancelCallback();
1124 CancelCallback FakeDriveService::ResumeUpload(
1125 const GURL& upload_url,
1126 int64 start_position,
1128 int64 content_length,
1129 const std::string& content_type,
1130 const base::FilePath& local_file_path,
1131 const UploadRangeCallback& callback,
1132 const ProgressCallback& progress_callback) {
1133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1134 DCHECK(!callback.is_null());
1136 GetResourceEntryCallback completion_callback
1137 = base::Bind(&ScheduleUploadRangeCallback,
1138 callback, start_position, end_position);
1141 completion_callback.Run(GDATA_NO_CONNECTION, scoped_ptr<ResourceEntry>());
1142 return CancelCallback();
1145 if (!upload_sessions_.count(upload_url)) {
1146 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1147 return CancelCallback();
1150 UploadSession* session = &upload_sessions_[upload_url];
1152 // Chunks are required to be sent in such a ways that they fill from the start
1153 // of the not-yet-uploaded part with no gaps nor overlaps.
1154 if (session->uploaded_size != start_position) {
1155 completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<ResourceEntry>());
1156 return CancelCallback();
1159 if (!progress_callback.is_null()) {
1160 // In the real GDataWapi/Drive DriveService, progress is reported in
1161 // nondeterministic timing. In this fake implementation, we choose to call
1162 // it twice per one ResumeUpload. This is for making sure that client code
1163 // works fine even if the callback is invoked more than once; it is the
1164 // crucial difference of the progress callback from others.
1165 // Note that progress is notified in the relative offset in each chunk.
1166 const int64 chunk_size = end_position - start_position;
1167 base::MessageLoop::current()->PostTask(
1168 FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size));
1169 base::MessageLoop::current()->PostTask(
1170 FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size));
1173 if (content_length != end_position) {
1174 session->uploaded_size = end_position;
1175 completion_callback.Run(HTTP_RESUME_INCOMPLETE,
1176 scoped_ptr<ResourceEntry>());
1177 return CancelCallback();
1180 std::string content_data;
1181 if (!base::ReadFileToString(local_file_path, &content_data)) {
1182 session->uploaded_size = end_position;
1183 completion_callback.Run(GDATA_FILE_ERROR, scoped_ptr<ResourceEntry>());
1184 return CancelCallback();
1186 session->uploaded_size = end_position;
1188 // |resource_id| is empty if the upload is for new file.
1189 if (session->resource_id.empty()) {
1190 DCHECK(!session->parent_resource_id.empty());
1191 DCHECK(!session->title.empty());
1192 const DictionaryValue* new_entry = AddNewEntry(
1193 session->content_type,
1195 session->parent_resource_id,
1197 false, // shared_with_me
1200 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1201 return CancelCallback();
1204 completion_callback.Run(HTTP_CREATED,
1205 ResourceEntry::CreateFrom(*new_entry));
1206 return CancelCallback();
1209 DictionaryValue* entry = FindEntryByResourceId(session->resource_id);
1211 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1212 return CancelCallback();
1215 std::string entry_etag;
1216 entry->GetString("gd$etag", &entry_etag);
1217 if (entry_etag.empty() || session->etag != entry_etag) {
1218 completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<ResourceEntry>());
1219 return CancelCallback();
1222 entry->SetString("docs$md5Checksum.$t", base::MD5String(content_data));
1223 entry->Set("test$data",
1224 base::BinaryValue::CreateWithCopiedBuffer(
1225 content_data.data(), content_data.size()));
1226 entry->SetString("docs$size.$t", base::Int64ToString(end_position));
1227 AddNewChangestampAndETag(entry);
1229 completion_callback.Run(HTTP_SUCCESS, ResourceEntry::CreateFrom(*entry));
1230 return CancelCallback();
1233 CancelCallback FakeDriveService::AuthorizeApp(
1234 const std::string& resource_id,
1235 const std::string& app_id,
1236 const AuthorizeAppCallback& callback) {
1237 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1238 DCHECK(!callback.is_null());
1239 return CancelCallback();
1242 CancelCallback FakeDriveService::GetResourceListInDirectoryByWapi(
1243 const std::string& directory_resource_id,
1244 const google_apis::GetResourceListCallback& callback) {
1245 return GetResourceListInDirectory(
1246 directory_resource_id == util::kWapiRootDirectoryResourceId ?
1247 GetRootResourceId() :
1248 directory_resource_id,
1252 CancelCallback FakeDriveService::GetRemainingResourceList(
1253 const GURL& next_link,
1254 const google_apis::GetResourceListCallback& callback) {
1255 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1256 DCHECK(!next_link.is_empty());
1257 DCHECK(!callback.is_null());
1259 // "changestamp", "q", "parent" and "start-offset" are parameters to
1260 // implement "paging" of the result on FakeDriveService.
1261 // The URL should be the one filled in GetResourceListInternal of the
1262 // previous method invocation, so it should start with "http://localhost/?".
1263 // See also GetResourceListInternal.
1264 DCHECK_EQ(next_link.host(), "localhost");
1265 DCHECK_EQ(next_link.path(), "/");
1267 int64 start_changestamp = 0;
1268 std::string search_query;
1269 std::string directory_resource_id;
1270 int start_offset = 0;
1271 int max_results = default_max_results_;
1272 std::vector<std::pair<std::string, std::string> > parameters;
1273 if (base::SplitStringIntoKeyValuePairs(
1274 next_link.query(), '=', '&', ¶meters)) {
1275 for (size_t i = 0; i < parameters.size(); ++i) {
1276 if (parameters[i].first == "changestamp") {
1277 base::StringToInt64(parameters[i].second, &start_changestamp);
1278 } else if (parameters[i].first == "q") {
1280 net::UnescapeURLComponent(parameters[i].second,
1281 net::UnescapeRule::URL_SPECIAL_CHARS);
1282 } else if (parameters[i].first == "parent") {
1283 directory_resource_id =
1284 net::UnescapeURLComponent(parameters[i].second,
1285 net::UnescapeRule::URL_SPECIAL_CHARS);
1286 } else if (parameters[i].first == "start-offset") {
1287 base::StringToInt(parameters[i].second, &start_offset);
1288 } else if (parameters[i].first == "max-results") {
1289 base::StringToInt(parameters[i].second, &max_results);
1294 GetResourceListInternal(
1295 start_changestamp, search_query, directory_resource_id,
1296 start_offset, max_results, NULL, callback);
1297 return CancelCallback();
1300 void FakeDriveService::AddNewFile(const std::string& content_type,
1301 const std::string& content_data,
1302 const std::string& parent_resource_id,
1303 const std::string& title,
1304 bool shared_with_me,
1305 const GetResourceEntryCallback& callback) {
1306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1307 DCHECK(!callback.is_null());
1310 scoped_ptr<ResourceEntry> null;
1311 base::MessageLoop::current()->PostTask(
1313 base::Bind(callback,
1314 GDATA_NO_CONNECTION,
1315 base::Passed(&null)));
1319 // Prepare "kind" for hosted documents. This only supports Google Document.
1320 std::string entry_kind;
1321 if (content_type == "application/vnd.google-apps.document")
1322 entry_kind = "document";
1324 entry_kind = "file";
1326 const base::DictionaryValue* new_entry = AddNewEntry(content_type,
1333 scoped_ptr<ResourceEntry> null;
1334 base::MessageLoop::current()->PostTask(
1336 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1340 scoped_ptr<ResourceEntry> parsed_entry(
1341 ResourceEntry::CreateFrom(*new_entry));
1342 base::MessageLoop::current()->PostTask(
1344 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
1347 void FakeDriveService::SetLastModifiedTime(
1348 const std::string& resource_id,
1349 const base::Time& last_modified_time,
1350 const GetResourceEntryCallback& callback) {
1351 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1352 DCHECK(!callback.is_null());
1355 scoped_ptr<ResourceEntry> null;
1356 base::MessageLoop::current()->PostTask(
1358 base::Bind(callback,
1359 GDATA_NO_CONNECTION,
1360 base::Passed(&null)));
1364 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
1366 scoped_ptr<ResourceEntry> null;
1367 base::MessageLoop::current()->PostTask(
1369 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1373 if (last_modified_time.is_null()) {
1374 entry->Remove("updated.$t", NULL);
1378 google_apis::util::FormatTimeAsString(last_modified_time));
1381 scoped_ptr<ResourceEntry> parsed_entry(
1382 ResourceEntry::CreateFrom(*entry));
1383 base::MessageLoop::current()->PostTask(
1385 base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry)));
1388 base::DictionaryValue* FakeDriveService::FindEntryByResourceId(
1389 const std::string& resource_id) {
1390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1392 base::ListValue* entries = NULL;
1393 // Go through entries and return the one that matches |resource_id|.
1394 if (resource_list_value_->GetList("entry", &entries)) {
1395 for (size_t i = 0; i < entries->GetSize(); ++i) {
1396 base::DictionaryValue* entry = NULL;
1397 std::string current_resource_id;
1398 if (entries->GetDictionary(i, &entry) &&
1399 entry->GetString("gd$resourceId.$t", ¤t_resource_id) &&
1400 resource_id == current_resource_id) {
1409 base::DictionaryValue* FakeDriveService::FindEntryByContentUrl(
1410 const GURL& content_url) {
1411 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1413 base::ListValue* entries = NULL;
1414 // Go through entries and return the one that matches |content_url|.
1415 if (resource_list_value_->GetList("entry", &entries)) {
1416 for (size_t i = 0; i < entries->GetSize(); ++i) {
1417 base::DictionaryValue* entry = NULL;
1418 std::string current_content_url;
1419 if (entries->GetDictionary(i, &entry) &&
1420 entry->GetString("content.src", ¤t_content_url) &&
1421 content_url == GURL(current_content_url)) {
1430 std::string FakeDriveService::GetNewResourceId() {
1431 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1433 ++resource_id_count_;
1434 return base::StringPrintf("resource_id_%d", resource_id_count_);
1437 void FakeDriveService::AddNewChangestampAndETag(base::DictionaryValue* entry) {
1438 ++largest_changestamp_;
1439 entry->SetString("docs$changestamp.value",
1440 base::Int64ToString(largest_changestamp_));
1441 entry->SetString("gd$etag",
1442 "etag_" + base::Int64ToString(largest_changestamp_));
1445 const base::DictionaryValue* FakeDriveService::AddNewEntry(
1446 const std::string& content_type,
1447 const std::string& content_data,
1448 const std::string& parent_resource_id,
1449 const std::string& title,
1450 bool shared_with_me,
1451 const std::string& entry_kind) {
1452 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1454 if (!parent_resource_id.empty() &&
1455 parent_resource_id != GetRootResourceId() &&
1456 !FindEntryByResourceId(parent_resource_id)) {
1460 std::string resource_id = GetNewResourceId();
1461 GURL upload_url = GURL("https://xxx/upload/" + resource_id);
1463 scoped_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue);
1464 // Set the resource ID and the title
1465 new_entry->SetString("gd$resourceId.$t", resource_id);
1466 new_entry->SetString("title.$t", title);
1467 new_entry->SetString("docs$filename", title);
1468 // Set the contents, size and MD5 for a file.
1469 if (entry_kind == "file") {
1470 new_entry->Set("test$data",
1471 base::BinaryValue::CreateWithCopiedBuffer(
1472 content_data.c_str(), content_data.size()));
1473 new_entry->SetString("docs$size.$t",
1474 base::Int64ToString(content_data.size()));
1475 new_entry->SetString("docs$md5Checksum.$t",
1476 base::MD5String(content_data));
1479 // Add "category" which sets the resource type to |entry_kind|.
1480 base::ListValue* categories = new base::ListValue;
1481 base::DictionaryValue* category = new base::DictionaryValue;
1482 category->SetString("scheme", "http://schemas.google.com/g/2005#kind");
1483 category->SetString("term", "http://schemas.google.com/docs/2007#" +
1485 categories->Append(category);
1486 new_entry->Set("category", categories);
1487 if (shared_with_me) {
1488 base::DictionaryValue* shared_with_me_label = new base::DictionaryValue;
1489 shared_with_me_label->SetString("label", "shared-with-me");
1490 shared_with_me_label->SetString("scheme",
1491 "http://schemas.google.com/g/2005/labels");
1492 shared_with_me_label->SetString(
1493 "term", "http://schemas.google.com/g/2005/labels#shared");
1494 categories->Append(shared_with_me_label);
1497 std::string escaped_resource_id = net::EscapePath(resource_id);
1499 // Add "content" which sets the content URL.
1500 base::DictionaryValue* content = new base::DictionaryValue;
1501 content->SetString("src", "https://xxx/content/" + escaped_resource_id);
1502 content->SetString("type", content_type);
1503 new_entry->Set("content", content);
1505 // Add "link" which sets the parent URL, the edit URL and the upload URL.
1506 base::ListValue* links = new base::ListValue;
1508 base::DictionaryValue* parent_link = new base::DictionaryValue;
1509 if (parent_resource_id.empty())
1510 parent_link->SetString("href", GetFakeLinkUrl(GetRootResourceId()).spec());
1512 parent_link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec());
1513 parent_link->SetString("rel",
1514 "http://schemas.google.com/docs/2007#parent");
1515 links->Append(parent_link);
1517 base::DictionaryValue* edit_link = new base::DictionaryValue;
1518 edit_link->SetString("href", "https://xxx/edit/" + escaped_resource_id);
1519 edit_link->SetString("rel", "edit");
1520 links->Append(edit_link);
1522 base::DictionaryValue* upload_link = new base::DictionaryValue;
1523 upload_link->SetString("href", upload_url.spec());
1524 upload_link->SetString("rel", kUploadUrlRel);
1525 links->Append(upload_link);
1527 const GURL share_url = net::AppendOrReplaceQueryParameter(
1528 share_url_base_, "name", title);
1529 base::DictionaryValue* share_link = new base::DictionaryValue;
1530 upload_link->SetString("href", share_url.spec());
1531 upload_link->SetString("rel", kShareUrlRel);
1532 links->Append(share_link);
1533 new_entry->Set("link", links);
1535 AddNewChangestampAndETag(new_entry.get());
1537 base::Time published_date =
1538 base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_);
1539 new_entry->SetString(
1541 google_apis::util::FormatTimeAsString(published_date));
1543 // If there are no entries, prepare an empty entry to add.
1544 if (!resource_list_value_->HasKey("entry"))
1545 resource_list_value_->Set("entry", new ListValue);
1547 base::DictionaryValue* raw_new_entry = new_entry.release();
1548 base::ListValue* entries = NULL;
1549 if (resource_list_value_->GetList("entry", &entries))
1550 entries->Append(raw_new_entry);
1552 return raw_new_entry;
1555 void FakeDriveService::GetResourceListInternal(
1556 int64 start_changestamp,
1557 const std::string& search_query,
1558 const std::string& directory_resource_id,
1562 const GetResourceListCallback& callback) {
1564 base::MessageLoop::current()->PostTask(
1566 base::Bind(callback,
1567 GDATA_NO_CONNECTION,
1568 base::Passed(scoped_ptr<ResourceList>())));
1572 scoped_ptr<ResourceList> resource_list =
1573 ResourceList::CreateFrom(*resource_list_value_);
1575 // TODO(hashimoto): Drive API always provides largest changestamp. Remove this
1576 // if-statement after API switch.
1577 if (start_changestamp > 0 && start_offset == 0)
1578 resource_list->set_largest_changestamp(largest_changestamp_);
1580 // Filter out entries per parameters like |directory_resource_id| and
1582 ScopedVector<ResourceEntry>* entries = resource_list->mutable_entries();
1584 int num_entries_matched = 0;
1585 for (size_t i = 0; i < entries->size();) {
1586 ResourceEntry* entry = (*entries)[i];
1587 bool should_exclude = false;
1589 // If |directory_resource_id| is set, exclude the entry if it's not in
1590 // the target directory.
1591 if (!directory_resource_id.empty()) {
1592 // Get the parent resource ID of the entry.
1593 std::string parent_resource_id;
1594 const google_apis::Link* parent_link =
1595 entry->GetLinkByType(Link::LINK_PARENT);
1597 parent_resource_id =
1598 net::UnescapeURLComponent(parent_link->href().ExtractFileName(),
1599 net::UnescapeRule::URL_SPECIAL_CHARS);
1601 if (directory_resource_id != parent_resource_id)
1602 should_exclude = true;
1605 // If |search_query| is set, exclude the entry if it does not contain the
1606 // search query in the title.
1607 if (!should_exclude && !search_query.empty() &&
1608 !EntryMatchWithQuery(*entry, search_query)) {
1609 should_exclude = true;
1612 // If |start_changestamp| is set, exclude the entry if the
1613 // changestamp is older than |largest_changestamp|.
1614 // See https://developers.google.com/google-apps/documents-list/
1615 // #retrieving_all_changes_since_a_given_changestamp
1616 if (start_changestamp > 0 && entry->changestamp() < start_changestamp)
1617 should_exclude = true;
1619 // If the caller requests other list than change list by specifying
1620 // zero-|start_changestamp|, exclude deleted entry from the result.
1621 if (!start_changestamp && entry->deleted())
1622 should_exclude = true;
1624 // The entry matched the criteria for inclusion.
1625 if (!should_exclude)
1626 ++num_entries_matched;
1628 // If |start_offset| is set, exclude the entry if the entry is before the
1629 // start index. <= instead of < as |num_entries_matched| was
1630 // already incremented.
1631 if (start_offset > 0 && num_entries_matched <= start_offset)
1632 should_exclude = true;
1635 entries->erase(entries->begin() + i);
1640 // If |max_results| is set, trim the entries if the number exceeded the max
1642 if (max_results > 0 && entries->size() > static_cast<size_t>(max_results)) {
1643 entries->erase(entries->begin() + max_results, entries->end());
1644 // Adds the next URL.
1645 // Here, we embed information which is needed for continuing the
1646 // GetResourceList request in the next invocation into url query
1648 GURL next_url(base::StringPrintf(
1649 "http://localhost/?start-offset=%d&max-results=%d",
1650 start_offset + max_results,
1652 if (start_changestamp > 0) {
1653 next_url = net::AppendOrReplaceQueryParameter(
1654 next_url, "changestamp",
1655 base::Int64ToString(start_changestamp).c_str());
1657 if (!search_query.empty()) {
1658 next_url = net::AppendOrReplaceQueryParameter(
1659 next_url, "q", search_query);
1661 if (!directory_resource_id.empty()) {
1662 next_url = net::AppendOrReplaceQueryParameter(
1663 next_url, "parent", directory_resource_id);
1666 Link* link = new Link;
1667 link->set_type(Link::LINK_NEXT);
1668 link->set_href(next_url);
1669 resource_list->mutable_links()->push_back(link);
1674 base::MessageLoop::current()->PostTask(
1676 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_list)));
1679 GURL FakeDriveService::GetNewUploadSessionUrl() {
1680 return GURL("https://upload_session_url/" +
1681 base::Int64ToString(next_upload_sequence_number_++));
1684 } // namespace drive