Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / drive / fake_drive_service.cc
blob2edde3bdeefb14f480b2e7d70925b07f42fd4414
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"
7 #include <string>
9 #include "base/file_util.h"
10 #include "base/logging.h"
11 #include "base/md5.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 "content/public/browser/browser_thread.h"
21 #include "google_apis/drive/drive_api_parser.h"
22 #include "google_apis/drive/gdata_wapi_parser.h"
23 #include "google_apis/drive/test_util.h"
24 #include "google_apis/drive/time_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::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::ChangeResource;
38 using google_apis::DownloadActionCallback;
39 using google_apis::EntryActionCallback;
40 using google_apis::FileResource;
41 using google_apis::GDATA_FILE_ERROR;
42 using google_apis::GDATA_NO_CONNECTION;
43 using google_apis::GDATA_OTHER_ERROR;
44 using google_apis::GDataErrorCode;
45 using google_apis::GetContentCallback;
46 using google_apis::GetResourceEntryCallback;
47 using google_apis::GetResourceListCallback;
48 using google_apis::GetShareUrlCallback;
49 using google_apis::HTTP_BAD_REQUEST;
50 using google_apis::HTTP_CREATED;
51 using google_apis::HTTP_NOT_FOUND;
52 using google_apis::HTTP_NO_CONTENT;
53 using google_apis::HTTP_PRECONDITION;
54 using google_apis::HTTP_RESUME_INCOMPLETE;
55 using google_apis::HTTP_SUCCESS;
56 using google_apis::InitiateUploadCallback;
57 using google_apis::Link;
58 using google_apis::ParentReference;
59 using google_apis::ProgressCallback;
60 using google_apis::ResourceEntry;
61 using google_apis::ResourceList;
62 using google_apis::UploadRangeCallback;
63 using google_apis::UploadRangeResponse;
64 namespace test_util = google_apis::test_util;
66 namespace drive {
67 namespace {
69 // Mime type of directories.
70 const char kDriveFolderMimeType[] = "application/vnd.google-apps.folder";
72 // Returns true if a resource entry matches with the search query.
73 // Supports queries consist of following format.
74 // - Phrases quoted by double/single quotes
75 // - AND search for multiple words/phrases segmented by space
76 // - Limited attribute search. Only "title:" is supported.
77 bool EntryMatchWithQuery(const ResourceEntry& entry,
78 const std::string& query) {
79 base::StringTokenizer tokenizer(query, " ");
80 tokenizer.set_quote_chars("\"'");
81 while (tokenizer.GetNext()) {
82 std::string key, value;
83 const std::string& token = tokenizer.token();
84 if (token.find(':') == std::string::npos) {
85 base::TrimString(token, "\"'", &value);
86 } else {
87 base::StringTokenizer key_value(token, ":");
88 key_value.set_quote_chars("\"'");
89 if (!key_value.GetNext())
90 return false;
91 key = key_value.token();
92 if (!key_value.GetNext())
93 return false;
94 base::TrimString(key_value.token(), "\"'", &value);
97 // TODO(peria): Deal with other attributes than title.
98 if (!key.empty() && key != "title")
99 return false;
100 // Search query in the title.
101 if (entry.title().find(value) == std::string::npos)
102 return false;
104 return true;
107 void ScheduleUploadRangeCallback(const UploadRangeCallback& callback,
108 int64 start_position,
109 int64 end_position,
110 GDataErrorCode error,
111 scoped_ptr<ResourceEntry> entry) {
112 base::MessageLoop::current()->PostTask(
113 FROM_HERE,
114 base::Bind(callback,
115 UploadRangeResponse(error,
116 start_position,
117 end_position),
118 base::Passed(&entry)));
121 void EntryActionCallbackAdapter(
122 const EntryActionCallback& callback,
123 GDataErrorCode error, scoped_ptr<ResourceEntry> resource_entry) {
124 callback.Run(error);
127 } // namespace
129 struct FakeDriveService::EntryInfo {
130 google_apis::ChangeResource change_resource;
131 GURL share_url;
132 std::string content_data;
135 struct FakeDriveService::UploadSession {
136 std::string content_type;
137 int64 content_length;
138 std::string parent_resource_id;
139 std::string resource_id;
140 std::string etag;
141 std::string title;
143 int64 uploaded_size;
145 UploadSession()
146 : content_length(0),
147 uploaded_size(0) {}
149 UploadSession(
150 std::string content_type,
151 int64 content_length,
152 std::string parent_resource_id,
153 std::string resource_id,
154 std::string etag,
155 std::string title)
156 : content_type(content_type),
157 content_length(content_length),
158 parent_resource_id(parent_resource_id),
159 resource_id(resource_id),
160 etag(etag),
161 title(title),
162 uploaded_size(0) {
166 FakeDriveService::FakeDriveService()
167 : largest_changestamp_(0),
168 published_date_seq_(0),
169 next_upload_sequence_number_(0),
170 default_max_results_(0),
171 resource_id_count_(0),
172 resource_list_load_count_(0),
173 change_list_load_count_(0),
174 directory_load_count_(0),
175 about_resource_load_count_(0),
176 app_list_load_count_(0),
177 blocked_resource_list_load_count_(0),
178 offline_(false),
179 never_return_all_resource_list_(false) {
180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
183 FakeDriveService::~FakeDriveService() {
184 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
185 STLDeleteValues(&entries_);
188 bool FakeDriveService::LoadResourceListForWapi(
189 const std::string& relative_path) {
190 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
191 scoped_ptr<base::Value> raw_value = test_util::LoadJSONFile(relative_path);
192 base::DictionaryValue* as_dict = NULL;
193 scoped_ptr<base::Value> feed;
194 base::DictionaryValue* feed_as_dict = NULL;
196 // Extract the "feed" from the raw value and take the ownership.
197 // Note that Remove() transfers the ownership to |feed|.
198 if (raw_value->GetAsDictionary(&as_dict) &&
199 as_dict->Remove("feed", &feed) &&
200 feed->GetAsDictionary(&feed_as_dict)) {
201 base::ListValue* entries = NULL;
202 if (feed_as_dict->GetList("entry", &entries)) {
203 for (size_t i = 0; i < entries->GetSize(); ++i) {
204 base::DictionaryValue* entry = NULL;
205 if (entries->GetDictionary(i, &entry)) {
206 scoped_ptr<ResourceEntry> resource_entry =
207 ResourceEntry::CreateFrom(*entry);
209 const std::string resource_id = resource_entry->resource_id();
210 EntryInfoMap::iterator it = entries_.find(resource_id);
211 if (it == entries_.end()) {
212 it = entries_.insert(
213 std::make_pair(resource_id, new EntryInfo)).first;
215 EntryInfo* new_entry = it->second;
217 ChangeResource* change = &new_entry->change_resource;
218 change->set_change_id(resource_entry->changestamp());
219 change->set_file_id(resource_id);
220 change->set_file(
221 util::ConvertResourceEntryToFileResource(*resource_entry));
223 const Link* share_url =
224 resource_entry->GetLinkByType(Link::LINK_SHARE);
225 if (share_url)
226 new_entry->share_url = share_url->href();
228 entry->GetString("test$data", &new_entry->content_data);
234 return feed_as_dict;
237 bool FakeDriveService::LoadAccountMetadataForWapi(
238 const std::string& relative_path) {
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
241 // Load JSON data, which must be a dictionary.
242 scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path);
243 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
244 account_metadata_value_.reset(
245 static_cast<base::DictionaryValue*>(value.release()));
247 // Update the largest_changestamp_.
248 scoped_ptr<AccountMetadata> account_metadata =
249 AccountMetadata::CreateFrom(*account_metadata_value_);
250 largest_changestamp_ = account_metadata->largest_changestamp();
252 // Add the largest changestamp to the existing entries.
253 // This will be used to generate change lists in GetResourceList().
254 for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end(); ++it)
255 it->second->change_resource.set_change_id(largest_changestamp_);
257 return account_metadata_value_;
260 bool FakeDriveService::LoadAppListForDriveApi(
261 const std::string& relative_path) {
262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
264 // Load JSON data, which must be a dictionary.
265 scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path);
266 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
267 app_info_value_.reset(
268 static_cast<base::DictionaryValue*>(value.release()));
269 return app_info_value_;
272 void FakeDriveService::SetQuotaValue(int64 used, int64 total) {
273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
274 DCHECK(account_metadata_value_);
276 account_metadata_value_->SetString("entry.gd$quotaBytesUsed.$t",
277 base::Int64ToString16(used));
278 account_metadata_value_->SetString("entry.gd$quotaBytesTotal.$t",
279 base::Int64ToString16(total));
282 GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
283 return GURL("https://fake_server/" + net::EscapePath(resource_id));
286 void FakeDriveService::Initialize(const std::string& account_id) {
287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
290 void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
291 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
294 void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298 bool FakeDriveService::CanSendRequest() const {
299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300 return true;
303 ResourceIdCanonicalizer FakeDriveService::GetResourceIdCanonicalizer() const {
304 return util::GetIdentityResourceIdCanonicalizer();
307 bool FakeDriveService::HasAccessToken() const {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
309 return true;
312 void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) {
313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
314 DCHECK(!callback.is_null());
315 callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
318 bool FakeDriveService::HasRefreshToken() const {
319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320 return true;
323 void FakeDriveService::ClearAccessToken() {
324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
327 void FakeDriveService::ClearRefreshToken() {
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
331 std::string FakeDriveService::GetRootResourceId() const {
332 return "fake_root";
335 CancelCallback FakeDriveService::GetAllResourceList(
336 const GetResourceListCallback& callback) {
337 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
338 DCHECK(!callback.is_null());
340 if (never_return_all_resource_list_) {
341 ++blocked_resource_list_load_count_;
342 return CancelCallback();
345 GetResourceListInternal(0, // start changestamp
346 std::string(), // empty search query
347 std::string(), // no directory resource id,
348 0, // start offset
349 default_max_results_,
350 &resource_list_load_count_,
351 callback);
352 return CancelCallback();
355 CancelCallback FakeDriveService::GetResourceListInDirectory(
356 const std::string& directory_resource_id,
357 const GetResourceListCallback& callback) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
359 DCHECK(!directory_resource_id.empty());
360 DCHECK(!callback.is_null());
362 GetResourceListInternal(0, // start changestamp
363 std::string(), // empty search query
364 directory_resource_id,
365 0, // start offset
366 default_max_results_,
367 &directory_load_count_,
368 callback);
369 return CancelCallback();
372 CancelCallback FakeDriveService::Search(
373 const std::string& search_query,
374 const GetResourceListCallback& callback) {
375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
376 DCHECK(!search_query.empty());
377 DCHECK(!callback.is_null());
379 GetResourceListInternal(0, // start changestamp
380 search_query,
381 std::string(), // no directory resource id,
382 0, // start offset
383 default_max_results_,
384 NULL,
385 callback);
386 return CancelCallback();
389 CancelCallback FakeDriveService::SearchByTitle(
390 const std::string& title,
391 const std::string& directory_resource_id,
392 const GetResourceListCallback& callback) {
393 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
394 DCHECK(!title.empty());
395 DCHECK(!callback.is_null());
397 // Note: the search implementation here doesn't support quotation unescape,
398 // so don't escape here.
399 GetResourceListInternal(0, // start changestamp
400 base::StringPrintf("title:'%s'", title.c_str()),
401 directory_resource_id,
402 0, // start offset
403 default_max_results_,
404 NULL,
405 callback);
406 return CancelCallback();
409 CancelCallback FakeDriveService::GetChangeList(
410 int64 start_changestamp,
411 const GetResourceListCallback& callback) {
412 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
413 DCHECK(!callback.is_null());
415 GetResourceListInternal(start_changestamp,
416 std::string(), // empty search query
417 std::string(), // no directory resource id,
418 0, // start offset
419 default_max_results_,
420 &change_list_load_count_,
421 callback);
422 return CancelCallback();
425 CancelCallback FakeDriveService::GetRemainingChangeList(
426 const GURL& next_link,
427 const GetResourceListCallback& callback) {
428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
429 DCHECK(!next_link.is_empty());
430 DCHECK(!callback.is_null());
432 return GetRemainingResourceList(next_link, callback);
435 CancelCallback FakeDriveService::GetRemainingFileList(
436 const GURL& next_link,
437 const GetResourceListCallback& callback) {
438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
439 DCHECK(!next_link.is_empty());
440 DCHECK(!callback.is_null());
442 return GetRemainingResourceList(next_link, callback);
445 CancelCallback FakeDriveService::GetResourceEntry(
446 const std::string& resource_id,
447 const GetResourceEntryCallback& callback) {
448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
449 DCHECK(!callback.is_null());
451 if (offline_) {
452 scoped_ptr<ResourceEntry> null;
453 base::MessageLoop::current()->PostTask(
454 FROM_HERE,
455 base::Bind(callback,
456 GDATA_NO_CONNECTION,
457 base::Passed(&null)));
458 return CancelCallback();
461 EntryInfo* entry = FindEntryByResourceId(resource_id);
462 if (entry) {
463 scoped_ptr<ResourceEntry> resource_entry =
464 util::ConvertChangeResourceToResourceEntry(entry->change_resource);
465 base::MessageLoop::current()->PostTask(
466 FROM_HERE,
467 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
468 return CancelCallback();
471 scoped_ptr<ResourceEntry> null;
472 base::MessageLoop::current()->PostTask(
473 FROM_HERE,
474 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
475 return CancelCallback();
478 CancelCallback FakeDriveService::GetShareUrl(
479 const std::string& resource_id,
480 const GURL& /* embed_origin */,
481 const GetShareUrlCallback& callback) {
482 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
483 DCHECK(!callback.is_null());
485 if (offline_) {
486 scoped_ptr<ResourceEntry> null;
487 base::MessageLoop::current()->PostTask(
488 FROM_HERE,
489 base::Bind(callback,
490 GDATA_NO_CONNECTION,
491 GURL()));
492 return CancelCallback();
495 EntryInfo* entry = FindEntryByResourceId(resource_id);
496 if (entry) {
497 base::MessageLoop::current()->PostTask(
498 FROM_HERE,
499 base::Bind(callback, HTTP_SUCCESS, entry->share_url));
500 return CancelCallback();
503 base::MessageLoop::current()->PostTask(
504 FROM_HERE,
505 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
506 return CancelCallback();
509 CancelCallback FakeDriveService::GetAboutResource(
510 const AboutResourceCallback& callback) {
511 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
512 DCHECK(!callback.is_null());
514 if (offline_) {
515 scoped_ptr<AboutResource> null;
516 base::MessageLoop::current()->PostTask(
517 FROM_HERE,
518 base::Bind(callback,
519 GDATA_NO_CONNECTION, base::Passed(&null)));
520 return CancelCallback();
523 ++about_resource_load_count_;
524 scoped_ptr<AboutResource> about_resource(
525 util::ConvertAccountMetadataToAboutResource(
526 *AccountMetadata::CreateFrom(*account_metadata_value_),
527 GetRootResourceId()));
528 // Overwrite the change id.
529 about_resource->set_largest_change_id(largest_changestamp_);
530 base::MessageLoop::current()->PostTask(
531 FROM_HERE,
532 base::Bind(callback,
533 HTTP_SUCCESS, base::Passed(&about_resource)));
534 return CancelCallback();
537 CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) {
538 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
539 DCHECK(!callback.is_null());
540 DCHECK(app_info_value_);
542 if (offline_) {
543 scoped_ptr<AppList> null;
544 base::MessageLoop::current()->PostTask(
545 FROM_HERE,
546 base::Bind(callback,
547 GDATA_NO_CONNECTION,
548 base::Passed(&null)));
549 return CancelCallback();
552 ++app_list_load_count_;
553 scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_));
554 base::MessageLoop::current()->PostTask(
555 FROM_HERE,
556 base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list)));
557 return CancelCallback();
560 CancelCallback FakeDriveService::DeleteResource(
561 const std::string& resource_id,
562 const std::string& etag,
563 const EntryActionCallback& callback) {
564 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
565 DCHECK(!callback.is_null());
567 if (offline_) {
568 base::MessageLoop::current()->PostTask(
569 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
570 return CancelCallback();
573 EntryInfo* entry = FindEntryByResourceId(resource_id);
574 if (entry) {
575 ChangeResource* change = &entry->change_resource;
576 const FileResource* file = change->file();
577 if (change->is_deleted()) {
578 base::MessageLoop::current()->PostTask(
579 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
580 return CancelCallback();
583 if (!etag.empty() && etag != file->etag()) {
584 base::MessageLoop::current()->PostTask(
585 FROM_HERE, base::Bind(callback, HTTP_PRECONDITION));
586 return CancelCallback();
589 change->set_deleted(true);
590 AddNewChangestamp(change);
591 change->set_file(scoped_ptr<FileResource>());
592 base::MessageLoop::current()->PostTask(
593 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
594 return CancelCallback();
597 base::MessageLoop::current()->PostTask(
598 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
599 return CancelCallback();
602 CancelCallback FakeDriveService::TrashResource(
603 const std::string& resource_id,
604 const EntryActionCallback& callback) {
605 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
606 DCHECK(!callback.is_null());
608 if (offline_) {
609 base::MessageLoop::current()->PostTask(
610 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
611 return CancelCallback();
614 EntryInfo* entry = FindEntryByResourceId(resource_id);
615 if (entry) {
616 ChangeResource* change = &entry->change_resource;
617 FileResource* file = change->mutable_file();
618 GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
619 if (change->is_deleted() || file->labels().is_trashed()) {
620 error = HTTP_NOT_FOUND;
621 } else {
622 file->mutable_labels()->set_trashed(true);
623 AddNewChangestamp(change);
624 error = HTTP_SUCCESS;
626 base::MessageLoop::current()->PostTask(
627 FROM_HERE, base::Bind(callback, error));
628 return CancelCallback();
631 base::MessageLoop::current()->PostTask(
632 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
633 return CancelCallback();
636 CancelCallback FakeDriveService::DownloadFile(
637 const base::FilePath& local_cache_path,
638 const std::string& resource_id,
639 const DownloadActionCallback& download_action_callback,
640 const GetContentCallback& get_content_callback,
641 const ProgressCallback& progress_callback) {
642 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
643 DCHECK(!download_action_callback.is_null());
645 if (offline_) {
646 base::MessageLoop::current()->PostTask(
647 FROM_HERE,
648 base::Bind(download_action_callback,
649 GDATA_NO_CONNECTION,
650 base::FilePath()));
651 return CancelCallback();
654 EntryInfo* entry = FindEntryByResourceId(resource_id);
655 if (!entry) {
656 base::MessageLoopProxy::current()->PostTask(
657 FROM_HERE,
658 base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath()));
659 return CancelCallback();
662 const FileResource* file = entry->change_resource.file();
663 const std::string& content_data = entry->content_data;
664 int64 file_size = file->file_size();
665 DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
667 if (!get_content_callback.is_null()) {
668 const int64 kBlockSize = 5;
669 for (int64 i = 0; i < file_size; i += kBlockSize) {
670 const int64 size = std::min(kBlockSize, file_size - i);
671 scoped_ptr<std::string> content_for_callback(
672 new std::string(content_data.substr(i, size)));
673 base::MessageLoopProxy::current()->PostTask(
674 FROM_HERE,
675 base::Bind(get_content_callback, HTTP_SUCCESS,
676 base::Passed(&content_for_callback)));
680 if (test_util::WriteStringToFile(local_cache_path, content_data)) {
681 if (!progress_callback.is_null()) {
682 // See also the comment in ResumeUpload(). For testing that clients
683 // can handle the case progress_callback is called multiple times,
684 // here we invoke the callback twice.
685 base::MessageLoopProxy::current()->PostTask(
686 FROM_HERE,
687 base::Bind(progress_callback, file_size / 2, file_size));
688 base::MessageLoopProxy::current()->PostTask(
689 FROM_HERE,
690 base::Bind(progress_callback, file_size, file_size));
692 base::MessageLoopProxy::current()->PostTask(
693 FROM_HERE,
694 base::Bind(download_action_callback,
695 HTTP_SUCCESS,
696 local_cache_path));
697 return CancelCallback();
700 // Failed to write the content.
701 base::MessageLoopProxy::current()->PostTask(
702 FROM_HERE,
703 base::Bind(download_action_callback, GDATA_FILE_ERROR, base::FilePath()));
704 return CancelCallback();
707 CancelCallback FakeDriveService::CopyResource(
708 const std::string& resource_id,
709 const std::string& in_parent_resource_id,
710 const std::string& new_title,
711 const base::Time& last_modified,
712 const GetResourceEntryCallback& callback) {
713 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
714 DCHECK(!callback.is_null());
716 if (offline_) {
717 scoped_ptr<ResourceEntry> null;
718 base::MessageLoop::current()->PostTask(
719 FROM_HERE,
720 base::Bind(callback,
721 GDATA_NO_CONNECTION,
722 base::Passed(&null)));
723 return CancelCallback();
726 const std::string& parent_resource_id = in_parent_resource_id.empty() ?
727 GetRootResourceId() : in_parent_resource_id;
729 EntryInfo* entry = FindEntryByResourceId(resource_id);
730 if (entry) {
731 // Make a copy and set the new resource ID and the new title.
732 scoped_ptr<EntryInfo> copied_entry(new EntryInfo);
733 copied_entry->content_data = entry->content_data;
734 copied_entry->share_url = entry->share_url;
736 // TODO(hashimoto): Implement a proper way to copy FileResource.
737 scoped_ptr<ResourceEntry> copied_resource_entry =
738 util::ConvertChangeResourceToResourceEntry(entry->change_resource);
739 copied_entry->change_resource.set_file(
740 util::ConvertResourceEntryToFileResource(*copied_resource_entry));
742 ChangeResource* new_change = &copied_entry->change_resource;
743 FileResource* new_file = new_change->mutable_file();
744 const std::string new_resource_id = GetNewResourceId();
745 new_change->set_file_id(new_resource_id);
746 new_file->set_file_id(new_resource_id);
747 new_file->set_title(new_title);
749 scoped_ptr<ParentReference> parent(new ParentReference);
750 parent->set_file_id(parent_resource_id);
751 parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
752 parent->set_is_root(parent_resource_id == GetRootResourceId());
753 ScopedVector<ParentReference> parents;
754 parents.push_back(parent.release());
755 new_file->set_parents(parents.Pass());
757 if (!last_modified.is_null())
758 new_file->set_modified_date(last_modified);
760 AddNewChangestamp(new_change);
761 UpdateETag(new_file);
763 scoped_ptr<ResourceEntry> resource_entry =
764 util::ConvertChangeResourceToResourceEntry(*new_change);
765 // Add the new entry to the map.
766 entries_[new_resource_id] = copied_entry.release();
768 base::MessageLoop::current()->PostTask(
769 FROM_HERE,
770 base::Bind(callback,
771 HTTP_SUCCESS,
772 base::Passed(&resource_entry)));
773 return CancelCallback();
776 scoped_ptr<ResourceEntry> null;
777 base::MessageLoop::current()->PostTask(
778 FROM_HERE,
779 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
780 return CancelCallback();
783 CancelCallback FakeDriveService::UpdateResource(
784 const std::string& resource_id,
785 const std::string& parent_resource_id,
786 const std::string& new_title,
787 const base::Time& last_modified,
788 const base::Time& last_viewed_by_me,
789 const google_apis::GetResourceEntryCallback& callback) {
790 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
791 DCHECK(!callback.is_null());
793 if (offline_) {
794 base::MessageLoop::current()->PostTask(
795 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION,
796 base::Passed(scoped_ptr<ResourceEntry>())));
797 return CancelCallback();
800 EntryInfo* entry = FindEntryByResourceId(resource_id);
801 if (entry) {
802 ChangeResource* change = &entry->change_resource;
803 FileResource* file = change->mutable_file();
804 file->set_title(new_title);
806 // Set parent if necessary.
807 if (!parent_resource_id.empty()) {
808 scoped_ptr<ParentReference> parent(new ParentReference);
809 parent->set_file_id(parent_resource_id);
810 parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
811 parent->set_is_root(parent_resource_id == GetRootResourceId());
813 ScopedVector<ParentReference> parents;
814 parents.push_back(parent.release());
815 file->set_parents(parents.Pass());
818 if (!last_modified.is_null())
819 file->set_modified_date(last_modified);
821 if (!last_viewed_by_me.is_null())
822 file->set_last_viewed_by_me_date(last_viewed_by_me);
824 AddNewChangestamp(change);
825 UpdateETag(file);
827 scoped_ptr<ResourceEntry> resource_entry =
828 util::ConvertChangeResourceToResourceEntry(*change);
829 base::MessageLoop::current()->PostTask(
830 FROM_HERE,
831 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
832 return CancelCallback();
835 base::MessageLoop::current()->PostTask(
836 FROM_HERE,
837 base::Bind(callback, HTTP_NOT_FOUND,
838 base::Passed(scoped_ptr<ResourceEntry>())));
839 return CancelCallback();
842 CancelCallback FakeDriveService::RenameResource(
843 const std::string& resource_id,
844 const std::string& new_title,
845 const EntryActionCallback& callback) {
846 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
847 DCHECK(!callback.is_null());
849 return UpdateResource(
850 resource_id, std::string(), new_title, base::Time(), base::Time(),
851 base::Bind(&EntryActionCallbackAdapter, callback));
854 CancelCallback FakeDriveService::AddResourceToDirectory(
855 const std::string& parent_resource_id,
856 const std::string& resource_id,
857 const EntryActionCallback& callback) {
858 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
859 DCHECK(!callback.is_null());
861 if (offline_) {
862 base::MessageLoop::current()->PostTask(
863 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
864 return CancelCallback();
867 EntryInfo* entry = FindEntryByResourceId(resource_id);
868 if (entry) {
869 ChangeResource* change = &entry->change_resource;
870 // On the real Drive server, resources do not necessary shape a tree
871 // structure. That is, each resource can have multiple parent.
872 // We mimic the behavior here; AddResourceToDirectoy just adds
873 // one more parent, not overwriting old ones.
874 scoped_ptr<ParentReference> parent(new ParentReference);
875 parent->set_file_id(parent_resource_id);
876 parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
877 parent->set_is_root(parent_resource_id == GetRootResourceId());
878 change->mutable_file()->mutable_parents()->push_back(parent.release());
880 AddNewChangestamp(change);
881 base::MessageLoop::current()->PostTask(
882 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
883 return CancelCallback();
886 base::MessageLoop::current()->PostTask(
887 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
888 return CancelCallback();
891 CancelCallback FakeDriveService::RemoveResourceFromDirectory(
892 const std::string& parent_resource_id,
893 const std::string& resource_id,
894 const EntryActionCallback& callback) {
895 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
896 DCHECK(!callback.is_null());
898 if (offline_) {
899 base::MessageLoop::current()->PostTask(
900 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
901 return CancelCallback();
904 EntryInfo* entry = FindEntryByResourceId(resource_id);
905 if (entry) {
906 ChangeResource* change = &entry->change_resource;
907 FileResource* file = change->mutable_file();
908 ScopedVector<ParentReference>* parents = file->mutable_parents();
909 for (size_t i = 0; i < parents->size(); ++i) {
910 if ((*parents)[i]->file_id() == parent_resource_id) {
911 parents->erase(parents->begin() + i);
912 AddNewChangestamp(change);
913 base::MessageLoop::current()->PostTask(
914 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
915 return CancelCallback();
920 base::MessageLoop::current()->PostTask(
921 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
922 return CancelCallback();
925 CancelCallback FakeDriveService::AddNewDirectory(
926 const std::string& parent_resource_id,
927 const std::string& directory_title,
928 const GetResourceEntryCallback& callback) {
929 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
930 DCHECK(!callback.is_null());
932 if (offline_) {
933 scoped_ptr<ResourceEntry> null;
934 base::MessageLoop::current()->PostTask(
935 FROM_HERE,
936 base::Bind(callback,
937 GDATA_NO_CONNECTION,
938 base::Passed(&null)));
939 return CancelCallback();
942 const EntryInfo* new_entry = AddNewEntry(kDriveFolderMimeType,
943 "", // content_data
944 parent_resource_id,
945 directory_title,
946 false); // shared_with_me
947 if (!new_entry) {
948 scoped_ptr<ResourceEntry> null;
949 base::MessageLoop::current()->PostTask(
950 FROM_HERE,
951 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
952 return CancelCallback();
955 scoped_ptr<ResourceEntry> parsed_entry(
956 util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
957 base::MessageLoop::current()->PostTask(
958 FROM_HERE,
959 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
960 return CancelCallback();
963 CancelCallback FakeDriveService::InitiateUploadNewFile(
964 const std::string& content_type,
965 int64 content_length,
966 const std::string& parent_resource_id,
967 const std::string& title,
968 const InitiateUploadCallback& callback) {
969 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
970 DCHECK(!callback.is_null());
972 if (offline_) {
973 base::MessageLoop::current()->PostTask(
974 FROM_HERE,
975 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
976 return CancelCallback();
979 if (parent_resource_id != GetRootResourceId() &&
980 !entries_.count(parent_resource_id)) {
981 base::MessageLoop::current()->PostTask(
982 FROM_HERE,
983 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
984 return CancelCallback();
987 GURL session_url = GetNewUploadSessionUrl();
988 upload_sessions_[session_url] =
989 UploadSession(content_type, content_length,
990 parent_resource_id,
991 "", // resource_id
992 "", // etag
993 title);
995 base::MessageLoop::current()->PostTask(
996 FROM_HERE,
997 base::Bind(callback, HTTP_SUCCESS, session_url));
998 return CancelCallback();
1001 CancelCallback FakeDriveService::InitiateUploadExistingFile(
1002 const std::string& content_type,
1003 int64 content_length,
1004 const std::string& resource_id,
1005 const std::string& etag,
1006 const InitiateUploadCallback& callback) {
1007 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1008 DCHECK(!callback.is_null());
1010 if (offline_) {
1011 base::MessageLoop::current()->PostTask(
1012 FROM_HERE,
1013 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
1014 return CancelCallback();
1017 EntryInfo* entry = FindEntryByResourceId(resource_id);
1018 if (!entry) {
1019 base::MessageLoop::current()->PostTask(
1020 FROM_HERE,
1021 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
1022 return CancelCallback();
1025 const FileResource* file = entry->change_resource.file();
1026 if (!etag.empty() && etag != file->etag()) {
1027 base::MessageLoop::current()->PostTask(
1028 FROM_HERE,
1029 base::Bind(callback, HTTP_PRECONDITION, GURL()));
1030 return CancelCallback();
1033 GURL session_url = GetNewUploadSessionUrl();
1034 upload_sessions_[session_url] =
1035 UploadSession(content_type, content_length,
1036 "", // parent_resource_id
1037 resource_id,
1038 file->etag(),
1039 "" /* title */);
1041 base::MessageLoop::current()->PostTask(
1042 FROM_HERE,
1043 base::Bind(callback, HTTP_SUCCESS, session_url));
1044 return CancelCallback();
1047 CancelCallback FakeDriveService::GetUploadStatus(
1048 const GURL& upload_url,
1049 int64 content_length,
1050 const UploadRangeCallback& callback) {
1051 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1052 DCHECK(!callback.is_null());
1053 return CancelCallback();
1056 CancelCallback FakeDriveService::ResumeUpload(
1057 const GURL& upload_url,
1058 int64 start_position,
1059 int64 end_position,
1060 int64 content_length,
1061 const std::string& content_type,
1062 const base::FilePath& local_file_path,
1063 const UploadRangeCallback& callback,
1064 const ProgressCallback& progress_callback) {
1065 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1066 DCHECK(!callback.is_null());
1068 GetResourceEntryCallback completion_callback
1069 = base::Bind(&ScheduleUploadRangeCallback,
1070 callback, start_position, end_position);
1072 if (offline_) {
1073 completion_callback.Run(GDATA_NO_CONNECTION, scoped_ptr<ResourceEntry>());
1074 return CancelCallback();
1077 if (!upload_sessions_.count(upload_url)) {
1078 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1079 return CancelCallback();
1082 UploadSession* session = &upload_sessions_[upload_url];
1084 // Chunks are required to be sent in such a ways that they fill from the start
1085 // of the not-yet-uploaded part with no gaps nor overlaps.
1086 if (session->uploaded_size != start_position) {
1087 completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<ResourceEntry>());
1088 return CancelCallback();
1091 if (!progress_callback.is_null()) {
1092 // In the real GDataWapi/Drive DriveService, progress is reported in
1093 // nondeterministic timing. In this fake implementation, we choose to call
1094 // it twice per one ResumeUpload. This is for making sure that client code
1095 // works fine even if the callback is invoked more than once; it is the
1096 // crucial difference of the progress callback from others.
1097 // Note that progress is notified in the relative offset in each chunk.
1098 const int64 chunk_size = end_position - start_position;
1099 base::MessageLoop::current()->PostTask(
1100 FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size));
1101 base::MessageLoop::current()->PostTask(
1102 FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size));
1105 if (content_length != end_position) {
1106 session->uploaded_size = end_position;
1107 completion_callback.Run(HTTP_RESUME_INCOMPLETE,
1108 scoped_ptr<ResourceEntry>());
1109 return CancelCallback();
1112 std::string content_data;
1113 if (!base::ReadFileToString(local_file_path, &content_data)) {
1114 session->uploaded_size = end_position;
1115 completion_callback.Run(GDATA_FILE_ERROR, scoped_ptr<ResourceEntry>());
1116 return CancelCallback();
1118 session->uploaded_size = end_position;
1120 // |resource_id| is empty if the upload is for new file.
1121 if (session->resource_id.empty()) {
1122 DCHECK(!session->parent_resource_id.empty());
1123 DCHECK(!session->title.empty());
1124 const EntryInfo* new_entry = AddNewEntry(
1125 session->content_type,
1126 content_data,
1127 session->parent_resource_id,
1128 session->title,
1129 false); // shared_with_me
1130 if (!new_entry) {
1131 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1132 return CancelCallback();
1135 completion_callback.Run(
1136 HTTP_CREATED,
1137 util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
1138 return CancelCallback();
1141 EntryInfo* entry = FindEntryByResourceId(session->resource_id);
1142 if (!entry) {
1143 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1144 return CancelCallback();
1147 ChangeResource* change = &entry->change_resource;
1148 FileResource* file = change->mutable_file();
1149 if (file->etag().empty() || session->etag != file->etag()) {
1150 completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<ResourceEntry>());
1151 return CancelCallback();
1154 file->set_md5_checksum(base::MD5String(content_data));
1155 entry->content_data = content_data;
1156 file->set_file_size(end_position);
1157 AddNewChangestamp(change);
1158 UpdateETag(file);
1160 completion_callback.Run(HTTP_SUCCESS,
1161 util::ConvertChangeResourceToResourceEntry(*change));
1162 return CancelCallback();
1165 CancelCallback FakeDriveService::AuthorizeApp(
1166 const std::string& resource_id,
1167 const std::string& app_id,
1168 const AuthorizeAppCallback& callback) {
1169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1170 DCHECK(!callback.is_null());
1171 return CancelCallback();
1174 CancelCallback FakeDriveService::UninstallApp(
1175 const std::string& app_id,
1176 const google_apis::EntryActionCallback& callback) {
1177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1178 DCHECK(!callback.is_null());
1180 // Find app_id from app_info_value_ and delete.
1181 google_apis::GDataErrorCode error = google_apis::HTTP_NOT_FOUND;
1182 if (offline_) {
1183 error = google_apis::GDATA_NO_CONNECTION;
1184 } else {
1185 base::ListValue* items = NULL;
1186 if (app_info_value_->GetList("items", &items)) {
1187 for (size_t i = 0; i < items->GetSize(); ++i) {
1188 base::DictionaryValue* item = NULL;
1189 std::string id;
1190 if (items->GetDictionary(i, &item) && item->GetString("id", &id) &&
1191 id == app_id) {
1192 if (items->Remove(i, NULL))
1193 error = google_apis::HTTP_NO_CONTENT;
1194 break;
1200 base::MessageLoop::current()->PostTask(FROM_HERE,
1201 base::Bind(callback, error));
1202 return CancelCallback();
1205 CancelCallback FakeDriveService::GetResourceListInDirectoryByWapi(
1206 const std::string& directory_resource_id,
1207 const google_apis::GetResourceListCallback& callback) {
1208 return GetResourceListInDirectory(
1209 directory_resource_id == util::kWapiRootDirectoryResourceId ?
1210 GetRootResourceId() :
1211 directory_resource_id,
1212 callback);
1215 CancelCallback FakeDriveService::GetRemainingResourceList(
1216 const GURL& next_link,
1217 const google_apis::GetResourceListCallback& callback) {
1218 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1219 DCHECK(!next_link.is_empty());
1220 DCHECK(!callback.is_null());
1222 // "changestamp", "q", "parent" and "start-offset" are parameters to
1223 // implement "paging" of the result on FakeDriveService.
1224 // The URL should be the one filled in GetResourceListInternal of the
1225 // previous method invocation, so it should start with "http://localhost/?".
1226 // See also GetResourceListInternal.
1227 DCHECK_EQ(next_link.host(), "localhost");
1228 DCHECK_EQ(next_link.path(), "/");
1230 int64 start_changestamp = 0;
1231 std::string search_query;
1232 std::string directory_resource_id;
1233 int start_offset = 0;
1234 int max_results = default_max_results_;
1235 std::vector<std::pair<std::string, std::string> > parameters;
1236 if (base::SplitStringIntoKeyValuePairs(
1237 next_link.query(), '=', '&', &parameters)) {
1238 for (size_t i = 0; i < parameters.size(); ++i) {
1239 if (parameters[i].first == "changestamp") {
1240 base::StringToInt64(parameters[i].second, &start_changestamp);
1241 } else if (parameters[i].first == "q") {
1242 search_query =
1243 net::UnescapeURLComponent(parameters[i].second,
1244 net::UnescapeRule::URL_SPECIAL_CHARS);
1245 } else if (parameters[i].first == "parent") {
1246 directory_resource_id =
1247 net::UnescapeURLComponent(parameters[i].second,
1248 net::UnescapeRule::URL_SPECIAL_CHARS);
1249 } else if (parameters[i].first == "start-offset") {
1250 base::StringToInt(parameters[i].second, &start_offset);
1251 } else if (parameters[i].first == "max-results") {
1252 base::StringToInt(parameters[i].second, &max_results);
1257 GetResourceListInternal(
1258 start_changestamp, search_query, directory_resource_id,
1259 start_offset, max_results, NULL, callback);
1260 return CancelCallback();
1263 void FakeDriveService::AddNewFile(const std::string& content_type,
1264 const std::string& content_data,
1265 const std::string& parent_resource_id,
1266 const std::string& title,
1267 bool shared_with_me,
1268 const GetResourceEntryCallback& callback) {
1269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1270 DCHECK(!callback.is_null());
1272 if (offline_) {
1273 scoped_ptr<ResourceEntry> null;
1274 base::MessageLoop::current()->PostTask(
1275 FROM_HERE,
1276 base::Bind(callback,
1277 GDATA_NO_CONNECTION,
1278 base::Passed(&null)));
1279 return;
1282 const EntryInfo* new_entry = AddNewEntry(content_type,
1283 content_data,
1284 parent_resource_id,
1285 title,
1286 shared_with_me);
1287 if (!new_entry) {
1288 scoped_ptr<ResourceEntry> null;
1289 base::MessageLoop::current()->PostTask(
1290 FROM_HERE,
1291 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1292 return;
1295 scoped_ptr<ResourceEntry> parsed_entry(
1296 util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
1297 base::MessageLoop::current()->PostTask(
1298 FROM_HERE,
1299 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
1302 void FakeDriveService::SetLastModifiedTime(
1303 const std::string& resource_id,
1304 const base::Time& last_modified_time,
1305 const GetResourceEntryCallback& callback) {
1306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1307 DCHECK(!callback.is_null());
1309 if (offline_) {
1310 scoped_ptr<ResourceEntry> null;
1311 base::MessageLoop::current()->PostTask(
1312 FROM_HERE,
1313 base::Bind(callback,
1314 GDATA_NO_CONNECTION,
1315 base::Passed(&null)));
1316 return;
1319 EntryInfo* entry = FindEntryByResourceId(resource_id);
1320 if (!entry) {
1321 scoped_ptr<ResourceEntry> null;
1322 base::MessageLoop::current()->PostTask(
1323 FROM_HERE,
1324 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1325 return;
1328 ChangeResource* change = &entry->change_resource;
1329 FileResource* file = change->mutable_file();
1330 file->set_modified_date(last_modified_time);
1332 scoped_ptr<ResourceEntry> parsed_entry(
1333 util::ConvertChangeResourceToResourceEntry(*change));
1334 base::MessageLoop::current()->PostTask(
1335 FROM_HERE,
1336 base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry)));
1339 FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId(
1340 const std::string& resource_id) {
1341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1343 EntryInfoMap::iterator it = entries_.find(resource_id);
1344 // Deleted entries don't have FileResource.
1345 return it != entries_.end() && it->second->change_resource.file() ?
1346 it->second : NULL;
1349 std::string FakeDriveService::GetNewResourceId() {
1350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1352 ++resource_id_count_;
1353 return base::StringPrintf("resource_id_%d", resource_id_count_);
1356 void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
1357 file->set_etag("etag_" + base::Int64ToString(largest_changestamp_));
1360 void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change) {
1361 ++largest_changestamp_;
1362 change->set_change_id(largest_changestamp_);
1365 const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry(
1366 const std::string& content_type,
1367 const std::string& content_data,
1368 const std::string& parent_resource_id,
1369 const std::string& title,
1370 bool shared_with_me) {
1371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1373 if (!parent_resource_id.empty() &&
1374 parent_resource_id != GetRootResourceId() &&
1375 !entries_.count(parent_resource_id)) {
1376 return NULL;
1379 std::string resource_id = GetNewResourceId();
1380 GURL upload_url = GURL("https://xxx/upload/" + resource_id);
1382 scoped_ptr<EntryInfo> new_entry(new EntryInfo);
1383 ChangeResource* new_change = &new_entry->change_resource;
1384 FileResource* new_file = new FileResource;
1385 new_change->set_file(make_scoped_ptr(new_file));
1387 // Set the resource ID and the title
1388 new_change->set_file_id(resource_id);
1389 new_file->set_file_id(resource_id);
1390 new_file->set_title(title);
1391 // Set the contents, size and MD5 for a file.
1392 if (content_type != kDriveFolderMimeType) {
1393 new_entry->content_data = content_data;
1394 new_file->set_file_size(content_data.size());
1395 new_file->set_md5_checksum(base::MD5String(content_data));
1398 if (shared_with_me) {
1399 // Set current time to mark the file as shared_with_me.
1400 new_file->set_shared_with_me_date(base::Time::Now());
1403 std::string escaped_resource_id = net::EscapePath(resource_id);
1405 // Set download URL and mime type.
1406 new_file->set_download_url(
1407 GURL("https://xxx/content/" + escaped_resource_id));
1408 new_file->set_mime_type(content_type);
1410 // Set parents.
1411 scoped_ptr<ParentReference> parent(new ParentReference);
1412 if (parent_resource_id.empty())
1413 parent->set_file_id(GetRootResourceId());
1414 else
1415 parent->set_file_id(parent_resource_id);
1416 parent->set_parent_link(GetFakeLinkUrl(parent->file_id()));
1417 parent->set_is_root(parent->file_id() == GetRootResourceId());
1418 ScopedVector<ParentReference> parents;
1419 parents.push_back(parent.release());
1420 new_file->set_parents(parents.Pass());
1422 new_file->set_self_link(GURL("https://xxx/edit/" + escaped_resource_id));
1424 new_entry->share_url = net::AppendOrReplaceQueryParameter(
1425 share_url_base_, "name", title);
1427 AddNewChangestamp(new_change);
1428 UpdateETag(new_file);
1430 base::Time published_date =
1431 base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_);
1432 new_file->set_created_date(published_date);
1434 EntryInfo* raw_new_entry = new_entry.release();
1435 entries_[resource_id] = raw_new_entry;
1436 return raw_new_entry;
1439 void FakeDriveService::GetResourceListInternal(
1440 int64 start_changestamp,
1441 const std::string& search_query,
1442 const std::string& directory_resource_id,
1443 int start_offset,
1444 int max_results,
1445 int* load_counter,
1446 const GetResourceListCallback& callback) {
1447 if (offline_) {
1448 base::MessageLoop::current()->PostTask(
1449 FROM_HERE,
1450 base::Bind(callback,
1451 GDATA_NO_CONNECTION,
1452 base::Passed(scoped_ptr<ResourceList>())));
1453 return;
1456 // Filter out entries per parameters like |directory_resource_id| and
1457 // |search_query|.
1458 ScopedVector<ResourceEntry> entries;
1459 int num_entries_matched = 0;
1460 for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end();
1461 ++it) {
1462 scoped_ptr<ResourceEntry> entry =
1463 util::ConvertChangeResourceToResourceEntry(it->second->change_resource);
1464 bool should_exclude = false;
1466 // If |directory_resource_id| is set, exclude the entry if it's not in
1467 // the target directory.
1468 if (!directory_resource_id.empty()) {
1469 // Get the parent resource ID of the entry.
1470 std::string parent_resource_id;
1471 const google_apis::Link* parent_link =
1472 entry->GetLinkByType(Link::LINK_PARENT);
1473 if (parent_link) {
1474 parent_resource_id =
1475 net::UnescapeURLComponent(parent_link->href().ExtractFileName(),
1476 net::UnescapeRule::URL_SPECIAL_CHARS);
1478 if (directory_resource_id != parent_resource_id)
1479 should_exclude = true;
1482 // If |search_query| is set, exclude the entry if it does not contain the
1483 // search query in the title.
1484 if (!should_exclude && !search_query.empty() &&
1485 !EntryMatchWithQuery(*entry, search_query)) {
1486 should_exclude = true;
1489 // If |start_changestamp| is set, exclude the entry if the
1490 // changestamp is older than |largest_changestamp|.
1491 // See https://developers.google.com/google-apps/documents-list/
1492 // #retrieving_all_changes_since_a_given_changestamp
1493 if (start_changestamp > 0 && entry->changestamp() < start_changestamp)
1494 should_exclude = true;
1496 // If the caller requests other list than change list by specifying
1497 // zero-|start_changestamp|, exclude deleted entry from the result.
1498 if (!start_changestamp && entry->deleted())
1499 should_exclude = true;
1501 // The entry matched the criteria for inclusion.
1502 if (!should_exclude)
1503 ++num_entries_matched;
1505 // If |start_offset| is set, exclude the entry if the entry is before the
1506 // start index. <= instead of < as |num_entries_matched| was
1507 // already incremented.
1508 if (start_offset > 0 && num_entries_matched <= start_offset)
1509 should_exclude = true;
1511 if (!should_exclude)
1512 entries.push_back(entry.release());
1515 scoped_ptr<ResourceList> resource_list(new ResourceList);
1516 if (start_changestamp > 0 && start_offset == 0)
1517 resource_list->set_largest_changestamp(largest_changestamp_);
1519 // If |max_results| is set, trim the entries if the number exceeded the max
1520 // results.
1521 if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) {
1522 entries.erase(entries.begin() + max_results, entries.end());
1523 // Adds the next URL.
1524 // Here, we embed information which is needed for continuing the
1525 // GetResourceList request in the next invocation into url query
1526 // parameters.
1527 GURL next_url(base::StringPrintf(
1528 "http://localhost/?start-offset=%d&max-results=%d",
1529 start_offset + max_results,
1530 max_results));
1531 if (start_changestamp > 0) {
1532 next_url = net::AppendOrReplaceQueryParameter(
1533 next_url, "changestamp",
1534 base::Int64ToString(start_changestamp).c_str());
1536 if (!search_query.empty()) {
1537 next_url = net::AppendOrReplaceQueryParameter(
1538 next_url, "q", search_query);
1540 if (!directory_resource_id.empty()) {
1541 next_url = net::AppendOrReplaceQueryParameter(
1542 next_url, "parent", directory_resource_id);
1545 Link* link = new Link;
1546 link->set_type(Link::LINK_NEXT);
1547 link->set_href(next_url);
1548 resource_list->mutable_links()->push_back(link);
1550 resource_list->set_entries(entries.Pass());
1552 if (load_counter)
1553 *load_counter += 1;
1554 base::MessageLoop::current()->PostTask(
1555 FROM_HERE,
1556 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_list)));
1559 GURL FakeDriveService::GetNewUploadSessionUrl() {
1560 return GURL("https://upload_session_url/" +
1561 base::Int64ToString(next_upload_sequence_number_++));
1564 } // namespace drive