NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / drive / fake_drive_service.cc
blob25298b8f0cd11f3684d90448e2fd6a473f627143
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 : about_resource_(new AboutResource),
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 scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path);
242 if (!value)
243 return false;
245 about_resource_ = util::ConvertAccountMetadataToAboutResource(
246 *AccountMetadata::CreateFrom(*value), GetRootResourceId());
247 if (!about_resource_)
248 return false;
250 // Add the largest changestamp to the existing entries.
251 // This will be used to generate change lists in GetResourceList().
252 for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end();
253 ++it) {
254 it->second->change_resource.set_change_id(
255 about_resource_->largest_change_id());
257 return true;
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));
275 about_resource_->set_quota_bytes_used(used);
276 about_resource_->set_quota_bytes_total(total);
279 GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
280 return GURL("https://fake_server/" + net::EscapePath(resource_id));
283 void FakeDriveService::Initialize(const std::string& account_id) {
284 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
287 void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
291 void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
292 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
295 bool FakeDriveService::CanSendRequest() const {
296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
297 return true;
300 ResourceIdCanonicalizer FakeDriveService::GetResourceIdCanonicalizer() const {
301 return util::GetIdentityResourceIdCanonicalizer();
304 bool FakeDriveService::HasAccessToken() const {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 return true;
309 void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) {
310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
311 DCHECK(!callback.is_null());
312 callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
315 bool FakeDriveService::HasRefreshToken() const {
316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
317 return true;
320 void FakeDriveService::ClearAccessToken() {
321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324 void FakeDriveService::ClearRefreshToken() {
325 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
328 std::string FakeDriveService::GetRootResourceId() const {
329 return "fake_root";
332 CancelCallback FakeDriveService::GetAllResourceList(
333 const GetResourceListCallback& callback) {
334 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
335 DCHECK(!callback.is_null());
337 if (never_return_all_resource_list_) {
338 ++blocked_resource_list_load_count_;
339 return CancelCallback();
342 GetResourceListInternal(0, // start changestamp
343 std::string(), // empty search query
344 std::string(), // no directory resource id,
345 0, // start offset
346 default_max_results_,
347 &resource_list_load_count_,
348 callback);
349 return CancelCallback();
352 CancelCallback FakeDriveService::GetResourceListInDirectory(
353 const std::string& directory_resource_id,
354 const GetResourceListCallback& callback) {
355 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
356 DCHECK(!directory_resource_id.empty());
357 DCHECK(!callback.is_null());
359 GetResourceListInternal(0, // start changestamp
360 std::string(), // empty search query
361 directory_resource_id,
362 0, // start offset
363 default_max_results_,
364 &directory_load_count_,
365 callback);
366 return CancelCallback();
369 CancelCallback FakeDriveService::Search(
370 const std::string& search_query,
371 const GetResourceListCallback& callback) {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
373 DCHECK(!search_query.empty());
374 DCHECK(!callback.is_null());
376 GetResourceListInternal(0, // start changestamp
377 search_query,
378 std::string(), // no directory resource id,
379 0, // start offset
380 default_max_results_,
381 NULL,
382 callback);
383 return CancelCallback();
386 CancelCallback FakeDriveService::SearchByTitle(
387 const std::string& title,
388 const std::string& directory_resource_id,
389 const GetResourceListCallback& callback) {
390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
391 DCHECK(!title.empty());
392 DCHECK(!callback.is_null());
394 // Note: the search implementation here doesn't support quotation unescape,
395 // so don't escape here.
396 GetResourceListInternal(0, // start changestamp
397 base::StringPrintf("title:'%s'", title.c_str()),
398 directory_resource_id,
399 0, // start offset
400 default_max_results_,
401 NULL,
402 callback);
403 return CancelCallback();
406 CancelCallback FakeDriveService::GetChangeList(
407 int64 start_changestamp,
408 const GetResourceListCallback& callback) {
409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
410 DCHECK(!callback.is_null());
412 GetResourceListInternal(start_changestamp,
413 std::string(), // empty search query
414 std::string(), // no directory resource id,
415 0, // start offset
416 default_max_results_,
417 &change_list_load_count_,
418 callback);
419 return CancelCallback();
422 CancelCallback FakeDriveService::GetRemainingChangeList(
423 const GURL& next_link,
424 const GetResourceListCallback& callback) {
425 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
426 DCHECK(!next_link.is_empty());
427 DCHECK(!callback.is_null());
429 return GetRemainingResourceList(next_link, callback);
432 CancelCallback FakeDriveService::GetRemainingFileList(
433 const GURL& next_link,
434 const GetResourceListCallback& callback) {
435 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
436 DCHECK(!next_link.is_empty());
437 DCHECK(!callback.is_null());
439 return GetRemainingResourceList(next_link, callback);
442 CancelCallback FakeDriveService::GetResourceEntry(
443 const std::string& resource_id,
444 const GetResourceEntryCallback& callback) {
445 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
446 DCHECK(!callback.is_null());
448 if (offline_) {
449 scoped_ptr<ResourceEntry> null;
450 base::MessageLoop::current()->PostTask(
451 FROM_HERE,
452 base::Bind(callback,
453 GDATA_NO_CONNECTION,
454 base::Passed(&null)));
455 return CancelCallback();
458 EntryInfo* entry = FindEntryByResourceId(resource_id);
459 if (entry) {
460 scoped_ptr<ResourceEntry> resource_entry =
461 util::ConvertChangeResourceToResourceEntry(entry->change_resource);
462 base::MessageLoop::current()->PostTask(
463 FROM_HERE,
464 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
465 return CancelCallback();
468 scoped_ptr<ResourceEntry> null;
469 base::MessageLoop::current()->PostTask(
470 FROM_HERE,
471 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
472 return CancelCallback();
475 CancelCallback FakeDriveService::GetShareUrl(
476 const std::string& resource_id,
477 const GURL& /* embed_origin */,
478 const GetShareUrlCallback& callback) {
479 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
480 DCHECK(!callback.is_null());
482 if (offline_) {
483 scoped_ptr<ResourceEntry> null;
484 base::MessageLoop::current()->PostTask(
485 FROM_HERE,
486 base::Bind(callback,
487 GDATA_NO_CONNECTION,
488 GURL()));
489 return CancelCallback();
492 EntryInfo* entry = FindEntryByResourceId(resource_id);
493 if (entry) {
494 base::MessageLoop::current()->PostTask(
495 FROM_HERE,
496 base::Bind(callback, HTTP_SUCCESS, entry->share_url));
497 return CancelCallback();
500 base::MessageLoop::current()->PostTask(
501 FROM_HERE,
502 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
503 return CancelCallback();
506 CancelCallback FakeDriveService::GetAboutResource(
507 const AboutResourceCallback& callback) {
508 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
509 DCHECK(!callback.is_null());
511 if (offline_) {
512 scoped_ptr<AboutResource> null;
513 base::MessageLoop::current()->PostTask(
514 FROM_HERE,
515 base::Bind(callback,
516 GDATA_NO_CONNECTION, base::Passed(&null)));
517 return CancelCallback();
520 ++about_resource_load_count_;
521 scoped_ptr<AboutResource> about_resource(new AboutResource(*about_resource_));
522 base::MessageLoop::current()->PostTask(
523 FROM_HERE,
524 base::Bind(callback,
525 HTTP_SUCCESS, base::Passed(&about_resource)));
526 return CancelCallback();
529 CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) {
530 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
531 DCHECK(!callback.is_null());
532 DCHECK(app_info_value_);
534 if (offline_) {
535 scoped_ptr<AppList> null;
536 base::MessageLoop::current()->PostTask(
537 FROM_HERE,
538 base::Bind(callback,
539 GDATA_NO_CONNECTION,
540 base::Passed(&null)));
541 return CancelCallback();
544 ++app_list_load_count_;
545 scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_));
546 base::MessageLoop::current()->PostTask(
547 FROM_HERE,
548 base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list)));
549 return CancelCallback();
552 CancelCallback FakeDriveService::DeleteResource(
553 const std::string& resource_id,
554 const std::string& etag,
555 const EntryActionCallback& callback) {
556 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
557 DCHECK(!callback.is_null());
559 if (offline_) {
560 base::MessageLoop::current()->PostTask(
561 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
562 return CancelCallback();
565 EntryInfo* entry = FindEntryByResourceId(resource_id);
566 if (entry) {
567 ChangeResource* change = &entry->change_resource;
568 const FileResource* file = change->file();
569 if (change->is_deleted()) {
570 base::MessageLoop::current()->PostTask(
571 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
572 return CancelCallback();
575 if (!etag.empty() && etag != file->etag()) {
576 base::MessageLoop::current()->PostTask(
577 FROM_HERE, base::Bind(callback, HTTP_PRECONDITION));
578 return CancelCallback();
581 change->set_deleted(true);
582 AddNewChangestamp(change);
583 change->set_file(scoped_ptr<FileResource>());
584 base::MessageLoop::current()->PostTask(
585 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
586 return CancelCallback();
589 base::MessageLoop::current()->PostTask(
590 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
591 return CancelCallback();
594 CancelCallback FakeDriveService::TrashResource(
595 const std::string& resource_id,
596 const EntryActionCallback& callback) {
597 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
598 DCHECK(!callback.is_null());
600 if (offline_) {
601 base::MessageLoop::current()->PostTask(
602 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
603 return CancelCallback();
606 EntryInfo* entry = FindEntryByResourceId(resource_id);
607 if (entry) {
608 ChangeResource* change = &entry->change_resource;
609 FileResource* file = change->mutable_file();
610 GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
611 if (change->is_deleted() || file->labels().is_trashed()) {
612 error = HTTP_NOT_FOUND;
613 } else {
614 file->mutable_labels()->set_trashed(true);
615 AddNewChangestamp(change);
616 error = HTTP_SUCCESS;
618 base::MessageLoop::current()->PostTask(
619 FROM_HERE, base::Bind(callback, error));
620 return CancelCallback();
623 base::MessageLoop::current()->PostTask(
624 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
625 return CancelCallback();
628 CancelCallback FakeDriveService::DownloadFile(
629 const base::FilePath& local_cache_path,
630 const std::string& resource_id,
631 const DownloadActionCallback& download_action_callback,
632 const GetContentCallback& get_content_callback,
633 const ProgressCallback& progress_callback) {
634 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
635 DCHECK(!download_action_callback.is_null());
637 if (offline_) {
638 base::MessageLoop::current()->PostTask(
639 FROM_HERE,
640 base::Bind(download_action_callback,
641 GDATA_NO_CONNECTION,
642 base::FilePath()));
643 return CancelCallback();
646 EntryInfo* entry = FindEntryByResourceId(resource_id);
647 if (!entry) {
648 base::MessageLoopProxy::current()->PostTask(
649 FROM_HERE,
650 base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath()));
651 return CancelCallback();
654 const FileResource* file = entry->change_resource.file();
655 const std::string& content_data = entry->content_data;
656 int64 file_size = file->file_size();
657 DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
659 if (!get_content_callback.is_null()) {
660 const int64 kBlockSize = 5;
661 for (int64 i = 0; i < file_size; i += kBlockSize) {
662 const int64 size = std::min(kBlockSize, file_size - i);
663 scoped_ptr<std::string> content_for_callback(
664 new std::string(content_data.substr(i, size)));
665 base::MessageLoopProxy::current()->PostTask(
666 FROM_HERE,
667 base::Bind(get_content_callback, HTTP_SUCCESS,
668 base::Passed(&content_for_callback)));
672 if (test_util::WriteStringToFile(local_cache_path, content_data)) {
673 if (!progress_callback.is_null()) {
674 // See also the comment in ResumeUpload(). For testing that clients
675 // can handle the case progress_callback is called multiple times,
676 // here we invoke the callback twice.
677 base::MessageLoopProxy::current()->PostTask(
678 FROM_HERE,
679 base::Bind(progress_callback, file_size / 2, file_size));
680 base::MessageLoopProxy::current()->PostTask(
681 FROM_HERE,
682 base::Bind(progress_callback, file_size, file_size));
684 base::MessageLoopProxy::current()->PostTask(
685 FROM_HERE,
686 base::Bind(download_action_callback,
687 HTTP_SUCCESS,
688 local_cache_path));
689 return CancelCallback();
692 // Failed to write the content.
693 base::MessageLoopProxy::current()->PostTask(
694 FROM_HERE,
695 base::Bind(download_action_callback, GDATA_FILE_ERROR, base::FilePath()));
696 return CancelCallback();
699 CancelCallback FakeDriveService::CopyResource(
700 const std::string& resource_id,
701 const std::string& in_parent_resource_id,
702 const std::string& new_title,
703 const base::Time& last_modified,
704 const GetResourceEntryCallback& callback) {
705 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
706 DCHECK(!callback.is_null());
708 if (offline_) {
709 scoped_ptr<ResourceEntry> null;
710 base::MessageLoop::current()->PostTask(
711 FROM_HERE,
712 base::Bind(callback,
713 GDATA_NO_CONNECTION,
714 base::Passed(&null)));
715 return CancelCallback();
718 const std::string& parent_resource_id = in_parent_resource_id.empty() ?
719 GetRootResourceId() : in_parent_resource_id;
721 EntryInfo* entry = FindEntryByResourceId(resource_id);
722 if (entry) {
723 // Make a copy and set the new resource ID and the new title.
724 scoped_ptr<EntryInfo> copied_entry(new EntryInfo);
725 copied_entry->content_data = entry->content_data;
726 copied_entry->share_url = entry->share_url;
728 // TODO(hashimoto): Implement a proper way to copy FileResource.
729 scoped_ptr<ResourceEntry> copied_resource_entry =
730 util::ConvertChangeResourceToResourceEntry(entry->change_resource);
731 copied_entry->change_resource.set_file(
732 util::ConvertResourceEntryToFileResource(*copied_resource_entry));
734 ChangeResource* new_change = &copied_entry->change_resource;
735 FileResource* new_file = new_change->mutable_file();
736 const std::string new_resource_id = GetNewResourceId();
737 new_change->set_file_id(new_resource_id);
738 new_file->set_file_id(new_resource_id);
739 new_file->set_title(new_title);
741 scoped_ptr<ParentReference> parent(new ParentReference);
742 parent->set_file_id(parent_resource_id);
743 parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
744 parent->set_is_root(parent_resource_id == GetRootResourceId());
745 ScopedVector<ParentReference> parents;
746 parents.push_back(parent.release());
747 new_file->set_parents(parents.Pass());
749 if (!last_modified.is_null())
750 new_file->set_modified_date(last_modified);
752 AddNewChangestamp(new_change);
753 UpdateETag(new_file);
755 scoped_ptr<ResourceEntry> resource_entry =
756 util::ConvertChangeResourceToResourceEntry(*new_change);
757 // Add the new entry to the map.
758 entries_[new_resource_id] = copied_entry.release();
760 base::MessageLoop::current()->PostTask(
761 FROM_HERE,
762 base::Bind(callback,
763 HTTP_SUCCESS,
764 base::Passed(&resource_entry)));
765 return CancelCallback();
768 scoped_ptr<ResourceEntry> null;
769 base::MessageLoop::current()->PostTask(
770 FROM_HERE,
771 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
772 return CancelCallback();
775 CancelCallback FakeDriveService::UpdateResource(
776 const std::string& resource_id,
777 const std::string& parent_resource_id,
778 const std::string& new_title,
779 const base::Time& last_modified,
780 const base::Time& last_viewed_by_me,
781 const google_apis::GetResourceEntryCallback& callback) {
782 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
783 DCHECK(!callback.is_null());
785 if (offline_) {
786 base::MessageLoop::current()->PostTask(
787 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION,
788 base::Passed(scoped_ptr<ResourceEntry>())));
789 return CancelCallback();
792 EntryInfo* entry = FindEntryByResourceId(resource_id);
793 if (entry) {
794 ChangeResource* change = &entry->change_resource;
795 FileResource* file = change->mutable_file();
796 file->set_title(new_title);
798 // Set parent if necessary.
799 if (!parent_resource_id.empty()) {
800 scoped_ptr<ParentReference> parent(new ParentReference);
801 parent->set_file_id(parent_resource_id);
802 parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
803 parent->set_is_root(parent_resource_id == GetRootResourceId());
805 ScopedVector<ParentReference> parents;
806 parents.push_back(parent.release());
807 file->set_parents(parents.Pass());
810 if (!last_modified.is_null())
811 file->set_modified_date(last_modified);
813 if (!last_viewed_by_me.is_null())
814 file->set_last_viewed_by_me_date(last_viewed_by_me);
816 AddNewChangestamp(change);
817 UpdateETag(file);
819 scoped_ptr<ResourceEntry> resource_entry =
820 util::ConvertChangeResourceToResourceEntry(*change);
821 base::MessageLoop::current()->PostTask(
822 FROM_HERE,
823 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
824 return CancelCallback();
827 base::MessageLoop::current()->PostTask(
828 FROM_HERE,
829 base::Bind(callback, HTTP_NOT_FOUND,
830 base::Passed(scoped_ptr<ResourceEntry>())));
831 return CancelCallback();
834 CancelCallback FakeDriveService::RenameResource(
835 const std::string& resource_id,
836 const std::string& new_title,
837 const EntryActionCallback& callback) {
838 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
839 DCHECK(!callback.is_null());
841 return UpdateResource(
842 resource_id, std::string(), new_title, base::Time(), base::Time(),
843 base::Bind(&EntryActionCallbackAdapter, callback));
846 CancelCallback FakeDriveService::AddResourceToDirectory(
847 const std::string& parent_resource_id,
848 const std::string& resource_id,
849 const EntryActionCallback& callback) {
850 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
851 DCHECK(!callback.is_null());
853 if (offline_) {
854 base::MessageLoop::current()->PostTask(
855 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
856 return CancelCallback();
859 EntryInfo* entry = FindEntryByResourceId(resource_id);
860 if (entry) {
861 ChangeResource* change = &entry->change_resource;
862 // On the real Drive server, resources do not necessary shape a tree
863 // structure. That is, each resource can have multiple parent.
864 // We mimic the behavior here; AddResourceToDirectoy just adds
865 // one more parent, not overwriting old ones.
866 scoped_ptr<ParentReference> parent(new ParentReference);
867 parent->set_file_id(parent_resource_id);
868 parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
869 parent->set_is_root(parent_resource_id == GetRootResourceId());
870 change->mutable_file()->mutable_parents()->push_back(parent.release());
872 AddNewChangestamp(change);
873 base::MessageLoop::current()->PostTask(
874 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
875 return CancelCallback();
878 base::MessageLoop::current()->PostTask(
879 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
880 return CancelCallback();
883 CancelCallback FakeDriveService::RemoveResourceFromDirectory(
884 const std::string& parent_resource_id,
885 const std::string& resource_id,
886 const EntryActionCallback& callback) {
887 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
888 DCHECK(!callback.is_null());
890 if (offline_) {
891 base::MessageLoop::current()->PostTask(
892 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
893 return CancelCallback();
896 EntryInfo* entry = FindEntryByResourceId(resource_id);
897 if (entry) {
898 ChangeResource* change = &entry->change_resource;
899 FileResource* file = change->mutable_file();
900 ScopedVector<ParentReference>* parents = file->mutable_parents();
901 for (size_t i = 0; i < parents->size(); ++i) {
902 if ((*parents)[i]->file_id() == parent_resource_id) {
903 parents->erase(parents->begin() + i);
904 AddNewChangestamp(change);
905 base::MessageLoop::current()->PostTask(
906 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
907 return CancelCallback();
912 base::MessageLoop::current()->PostTask(
913 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
914 return CancelCallback();
917 CancelCallback FakeDriveService::AddNewDirectory(
918 const std::string& parent_resource_id,
919 const std::string& directory_title,
920 const AddNewDirectoryOptions& options,
921 const GetResourceEntryCallback& callback) {
922 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
923 DCHECK(!callback.is_null());
925 if (offline_) {
926 scoped_ptr<ResourceEntry> null;
927 base::MessageLoop::current()->PostTask(
928 FROM_HERE,
929 base::Bind(callback,
930 GDATA_NO_CONNECTION,
931 base::Passed(&null)));
932 return CancelCallback();
935 const EntryInfo* new_entry = AddNewEntry(kDriveFolderMimeType,
936 "", // content_data
937 parent_resource_id,
938 directory_title,
939 false); // shared_with_me
940 if (!new_entry) {
941 scoped_ptr<ResourceEntry> null;
942 base::MessageLoop::current()->PostTask(
943 FROM_HERE,
944 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
945 return CancelCallback();
948 scoped_ptr<ResourceEntry> parsed_entry(
949 util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
950 base::MessageLoop::current()->PostTask(
951 FROM_HERE,
952 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
953 return CancelCallback();
956 CancelCallback FakeDriveService::InitiateUploadNewFile(
957 const std::string& content_type,
958 int64 content_length,
959 const std::string& parent_resource_id,
960 const std::string& title,
961 const InitiateUploadNewFileOptions& options,
962 const InitiateUploadCallback& callback) {
963 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
964 DCHECK(!callback.is_null());
966 if (offline_) {
967 base::MessageLoop::current()->PostTask(
968 FROM_HERE,
969 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
970 return CancelCallback();
973 if (parent_resource_id != GetRootResourceId() &&
974 !entries_.count(parent_resource_id)) {
975 base::MessageLoop::current()->PostTask(
976 FROM_HERE,
977 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
978 return CancelCallback();
981 GURL session_url = GetNewUploadSessionUrl();
982 upload_sessions_[session_url] =
983 UploadSession(content_type, content_length,
984 parent_resource_id,
985 "", // resource_id
986 "", // etag
987 title);
989 base::MessageLoop::current()->PostTask(
990 FROM_HERE,
991 base::Bind(callback, HTTP_SUCCESS, session_url));
992 return CancelCallback();
995 CancelCallback FakeDriveService::InitiateUploadExistingFile(
996 const std::string& content_type,
997 int64 content_length,
998 const std::string& resource_id,
999 const InitiateUploadExistingFileOptions& options,
1000 const InitiateUploadCallback& callback) {
1001 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1002 DCHECK(!callback.is_null());
1004 if (offline_) {
1005 base::MessageLoop::current()->PostTask(
1006 FROM_HERE,
1007 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
1008 return CancelCallback();
1011 EntryInfo* entry = FindEntryByResourceId(resource_id);
1012 if (!entry) {
1013 base::MessageLoop::current()->PostTask(
1014 FROM_HERE,
1015 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
1016 return CancelCallback();
1019 FileResource* file = entry->change_resource.mutable_file();
1020 if (!options.etag.empty() && options.etag != file->etag()) {
1021 base::MessageLoop::current()->PostTask(
1022 FROM_HERE,
1023 base::Bind(callback, HTTP_PRECONDITION, GURL()));
1024 return CancelCallback();
1026 // TODO(hashimoto): Update |file|'s metadata with |options|.
1028 GURL session_url = GetNewUploadSessionUrl();
1029 upload_sessions_[session_url] =
1030 UploadSession(content_type, content_length,
1031 "", // parent_resource_id
1032 resource_id,
1033 file->etag(),
1034 "" /* title */);
1036 base::MessageLoop::current()->PostTask(
1037 FROM_HERE,
1038 base::Bind(callback, HTTP_SUCCESS, session_url));
1039 return CancelCallback();
1042 CancelCallback FakeDriveService::GetUploadStatus(
1043 const GURL& upload_url,
1044 int64 content_length,
1045 const UploadRangeCallback& callback) {
1046 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1047 DCHECK(!callback.is_null());
1048 return CancelCallback();
1051 CancelCallback FakeDriveService::ResumeUpload(
1052 const GURL& upload_url,
1053 int64 start_position,
1054 int64 end_position,
1055 int64 content_length,
1056 const std::string& content_type,
1057 const base::FilePath& local_file_path,
1058 const UploadRangeCallback& callback,
1059 const ProgressCallback& progress_callback) {
1060 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1061 DCHECK(!callback.is_null());
1063 GetResourceEntryCallback completion_callback
1064 = base::Bind(&ScheduleUploadRangeCallback,
1065 callback, start_position, end_position);
1067 if (offline_) {
1068 completion_callback.Run(GDATA_NO_CONNECTION, scoped_ptr<ResourceEntry>());
1069 return CancelCallback();
1072 if (!upload_sessions_.count(upload_url)) {
1073 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1074 return CancelCallback();
1077 UploadSession* session = &upload_sessions_[upload_url];
1079 // Chunks are required to be sent in such a ways that they fill from the start
1080 // of the not-yet-uploaded part with no gaps nor overlaps.
1081 if (session->uploaded_size != start_position) {
1082 completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<ResourceEntry>());
1083 return CancelCallback();
1086 if (!progress_callback.is_null()) {
1087 // In the real GDataWapi/Drive DriveService, progress is reported in
1088 // nondeterministic timing. In this fake implementation, we choose to call
1089 // it twice per one ResumeUpload. This is for making sure that client code
1090 // works fine even if the callback is invoked more than once; it is the
1091 // crucial difference of the progress callback from others.
1092 // Note that progress is notified in the relative offset in each chunk.
1093 const int64 chunk_size = end_position - start_position;
1094 base::MessageLoop::current()->PostTask(
1095 FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size));
1096 base::MessageLoop::current()->PostTask(
1097 FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size));
1100 if (content_length != end_position) {
1101 session->uploaded_size = end_position;
1102 completion_callback.Run(HTTP_RESUME_INCOMPLETE,
1103 scoped_ptr<ResourceEntry>());
1104 return CancelCallback();
1107 std::string content_data;
1108 if (!base::ReadFileToString(local_file_path, &content_data)) {
1109 session->uploaded_size = end_position;
1110 completion_callback.Run(GDATA_FILE_ERROR, scoped_ptr<ResourceEntry>());
1111 return CancelCallback();
1113 session->uploaded_size = end_position;
1115 // |resource_id| is empty if the upload is for new file.
1116 if (session->resource_id.empty()) {
1117 DCHECK(!session->parent_resource_id.empty());
1118 DCHECK(!session->title.empty());
1119 const EntryInfo* new_entry = AddNewEntry(
1120 session->content_type,
1121 content_data,
1122 session->parent_resource_id,
1123 session->title,
1124 false); // shared_with_me
1125 if (!new_entry) {
1126 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1127 return CancelCallback();
1130 completion_callback.Run(
1131 HTTP_CREATED,
1132 util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
1133 return CancelCallback();
1136 EntryInfo* entry = FindEntryByResourceId(session->resource_id);
1137 if (!entry) {
1138 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
1139 return CancelCallback();
1142 ChangeResource* change = &entry->change_resource;
1143 FileResource* file = change->mutable_file();
1144 if (file->etag().empty() || session->etag != file->etag()) {
1145 completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<ResourceEntry>());
1146 return CancelCallback();
1149 file->set_md5_checksum(base::MD5String(content_data));
1150 entry->content_data = content_data;
1151 file->set_file_size(end_position);
1152 AddNewChangestamp(change);
1153 UpdateETag(file);
1155 completion_callback.Run(HTTP_SUCCESS,
1156 util::ConvertChangeResourceToResourceEntry(*change));
1157 return CancelCallback();
1160 CancelCallback FakeDriveService::AuthorizeApp(
1161 const std::string& resource_id,
1162 const std::string& app_id,
1163 const AuthorizeAppCallback& callback) {
1164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1165 DCHECK(!callback.is_null());
1166 return CancelCallback();
1169 CancelCallback FakeDriveService::UninstallApp(
1170 const std::string& app_id,
1171 const google_apis::EntryActionCallback& callback) {
1172 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1173 DCHECK(!callback.is_null());
1175 // Find app_id from app_info_value_ and delete.
1176 google_apis::GDataErrorCode error = google_apis::HTTP_NOT_FOUND;
1177 if (offline_) {
1178 error = google_apis::GDATA_NO_CONNECTION;
1179 } else {
1180 base::ListValue* items = NULL;
1181 if (app_info_value_->GetList("items", &items)) {
1182 for (size_t i = 0; i < items->GetSize(); ++i) {
1183 base::DictionaryValue* item = NULL;
1184 std::string id;
1185 if (items->GetDictionary(i, &item) && item->GetString("id", &id) &&
1186 id == app_id) {
1187 if (items->Remove(i, NULL))
1188 error = google_apis::HTTP_NO_CONTENT;
1189 break;
1195 base::MessageLoop::current()->PostTask(FROM_HERE,
1196 base::Bind(callback, error));
1197 return CancelCallback();
1200 CancelCallback FakeDriveService::GetResourceListInDirectoryByWapi(
1201 const std::string& directory_resource_id,
1202 const google_apis::GetResourceListCallback& callback) {
1203 return GetResourceListInDirectory(
1204 directory_resource_id == util::kWapiRootDirectoryResourceId ?
1205 GetRootResourceId() :
1206 directory_resource_id,
1207 callback);
1210 CancelCallback FakeDriveService::GetRemainingResourceList(
1211 const GURL& next_link,
1212 const google_apis::GetResourceListCallback& callback) {
1213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1214 DCHECK(!next_link.is_empty());
1215 DCHECK(!callback.is_null());
1217 // "changestamp", "q", "parent" and "start-offset" are parameters to
1218 // implement "paging" of the result on FakeDriveService.
1219 // The URL should be the one filled in GetResourceListInternal of the
1220 // previous method invocation, so it should start with "http://localhost/?".
1221 // See also GetResourceListInternal.
1222 DCHECK_EQ(next_link.host(), "localhost");
1223 DCHECK_EQ(next_link.path(), "/");
1225 int64 start_changestamp = 0;
1226 std::string search_query;
1227 std::string directory_resource_id;
1228 int start_offset = 0;
1229 int max_results = default_max_results_;
1230 std::vector<std::pair<std::string, std::string> > parameters;
1231 if (base::SplitStringIntoKeyValuePairs(
1232 next_link.query(), '=', '&', &parameters)) {
1233 for (size_t i = 0; i < parameters.size(); ++i) {
1234 if (parameters[i].first == "changestamp") {
1235 base::StringToInt64(parameters[i].second, &start_changestamp);
1236 } else if (parameters[i].first == "q") {
1237 search_query =
1238 net::UnescapeURLComponent(parameters[i].second,
1239 net::UnescapeRule::URL_SPECIAL_CHARS);
1240 } else if (parameters[i].first == "parent") {
1241 directory_resource_id =
1242 net::UnescapeURLComponent(parameters[i].second,
1243 net::UnescapeRule::URL_SPECIAL_CHARS);
1244 } else if (parameters[i].first == "start-offset") {
1245 base::StringToInt(parameters[i].second, &start_offset);
1246 } else if (parameters[i].first == "max-results") {
1247 base::StringToInt(parameters[i].second, &max_results);
1252 GetResourceListInternal(
1253 start_changestamp, search_query, directory_resource_id,
1254 start_offset, max_results, NULL, callback);
1255 return CancelCallback();
1258 void FakeDriveService::AddNewFile(const std::string& content_type,
1259 const std::string& content_data,
1260 const std::string& parent_resource_id,
1261 const std::string& title,
1262 bool shared_with_me,
1263 const GetResourceEntryCallback& callback) {
1264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1265 DCHECK(!callback.is_null());
1267 if (offline_) {
1268 scoped_ptr<ResourceEntry> null;
1269 base::MessageLoop::current()->PostTask(
1270 FROM_HERE,
1271 base::Bind(callback,
1272 GDATA_NO_CONNECTION,
1273 base::Passed(&null)));
1274 return;
1277 const EntryInfo* new_entry = AddNewEntry(content_type,
1278 content_data,
1279 parent_resource_id,
1280 title,
1281 shared_with_me);
1282 if (!new_entry) {
1283 scoped_ptr<ResourceEntry> null;
1284 base::MessageLoop::current()->PostTask(
1285 FROM_HERE,
1286 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1287 return;
1290 scoped_ptr<ResourceEntry> parsed_entry(
1291 util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
1292 base::MessageLoop::current()->PostTask(
1293 FROM_HERE,
1294 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
1297 void FakeDriveService::SetLastModifiedTime(
1298 const std::string& resource_id,
1299 const base::Time& last_modified_time,
1300 const GetResourceEntryCallback& callback) {
1301 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1302 DCHECK(!callback.is_null());
1304 if (offline_) {
1305 scoped_ptr<ResourceEntry> null;
1306 base::MessageLoop::current()->PostTask(
1307 FROM_HERE,
1308 base::Bind(callback,
1309 GDATA_NO_CONNECTION,
1310 base::Passed(&null)));
1311 return;
1314 EntryInfo* entry = FindEntryByResourceId(resource_id);
1315 if (!entry) {
1316 scoped_ptr<ResourceEntry> null;
1317 base::MessageLoop::current()->PostTask(
1318 FROM_HERE,
1319 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1320 return;
1323 ChangeResource* change = &entry->change_resource;
1324 FileResource* file = change->mutable_file();
1325 file->set_modified_date(last_modified_time);
1327 scoped_ptr<ResourceEntry> parsed_entry(
1328 util::ConvertChangeResourceToResourceEntry(*change));
1329 base::MessageLoop::current()->PostTask(
1330 FROM_HERE,
1331 base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry)));
1334 FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId(
1335 const std::string& resource_id) {
1336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1338 EntryInfoMap::iterator it = entries_.find(resource_id);
1339 // Deleted entries don't have FileResource.
1340 return it != entries_.end() && it->second->change_resource.file() ?
1341 it->second : NULL;
1344 std::string FakeDriveService::GetNewResourceId() {
1345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1347 ++resource_id_count_;
1348 return base::StringPrintf("resource_id_%d", resource_id_count_);
1351 void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
1352 file->set_etag(
1353 "etag_" + base::Int64ToString(about_resource_->largest_change_id()));
1356 void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change) {
1357 about_resource_->set_largest_change_id(
1358 about_resource_->largest_change_id() + 1);
1359 change->set_change_id(about_resource_->largest_change_id());
1362 const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry(
1363 const std::string& content_type,
1364 const std::string& content_data,
1365 const std::string& parent_resource_id,
1366 const std::string& title,
1367 bool shared_with_me) {
1368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1370 if (!parent_resource_id.empty() &&
1371 parent_resource_id != GetRootResourceId() &&
1372 !entries_.count(parent_resource_id)) {
1373 return NULL;
1376 std::string resource_id = GetNewResourceId();
1377 GURL upload_url = GURL("https://xxx/upload/" + resource_id);
1379 scoped_ptr<EntryInfo> new_entry(new EntryInfo);
1380 ChangeResource* new_change = &new_entry->change_resource;
1381 FileResource* new_file = new FileResource;
1382 new_change->set_file(make_scoped_ptr(new_file));
1384 // Set the resource ID and the title
1385 new_change->set_file_id(resource_id);
1386 new_file->set_file_id(resource_id);
1387 new_file->set_title(title);
1388 // Set the contents, size and MD5 for a file.
1389 if (content_type != kDriveFolderMimeType) {
1390 new_entry->content_data = content_data;
1391 new_file->set_file_size(content_data.size());
1392 new_file->set_md5_checksum(base::MD5String(content_data));
1395 if (shared_with_me) {
1396 // Set current time to mark the file as shared_with_me.
1397 new_file->set_shared_with_me_date(base::Time::Now());
1400 std::string escaped_resource_id = net::EscapePath(resource_id);
1402 // Set download URL and mime type.
1403 new_file->set_download_url(
1404 GURL("https://xxx/content/" + escaped_resource_id));
1405 new_file->set_mime_type(content_type);
1407 // Set parents.
1408 scoped_ptr<ParentReference> parent(new ParentReference);
1409 if (parent_resource_id.empty())
1410 parent->set_file_id(GetRootResourceId());
1411 else
1412 parent->set_file_id(parent_resource_id);
1413 parent->set_parent_link(GetFakeLinkUrl(parent->file_id()));
1414 parent->set_is_root(parent->file_id() == GetRootResourceId());
1415 ScopedVector<ParentReference> parents;
1416 parents.push_back(parent.release());
1417 new_file->set_parents(parents.Pass());
1419 new_file->set_self_link(GURL("https://xxx/edit/" + escaped_resource_id));
1421 new_entry->share_url = net::AppendOrReplaceQueryParameter(
1422 share_url_base_, "name", title);
1424 AddNewChangestamp(new_change);
1425 UpdateETag(new_file);
1427 base::Time published_date =
1428 base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_);
1429 new_file->set_created_date(published_date);
1431 EntryInfo* raw_new_entry = new_entry.release();
1432 entries_[resource_id] = raw_new_entry;
1433 return raw_new_entry;
1436 void FakeDriveService::GetResourceListInternal(
1437 int64 start_changestamp,
1438 const std::string& search_query,
1439 const std::string& directory_resource_id,
1440 int start_offset,
1441 int max_results,
1442 int* load_counter,
1443 const GetResourceListCallback& callback) {
1444 if (offline_) {
1445 base::MessageLoop::current()->PostTask(
1446 FROM_HERE,
1447 base::Bind(callback,
1448 GDATA_NO_CONNECTION,
1449 base::Passed(scoped_ptr<ResourceList>())));
1450 return;
1453 // Filter out entries per parameters like |directory_resource_id| and
1454 // |search_query|.
1455 ScopedVector<ResourceEntry> entries;
1456 int num_entries_matched = 0;
1457 for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end();
1458 ++it) {
1459 scoped_ptr<ResourceEntry> entry =
1460 util::ConvertChangeResourceToResourceEntry(it->second->change_resource);
1461 bool should_exclude = false;
1463 // If |directory_resource_id| is set, exclude the entry if it's not in
1464 // the target directory.
1465 if (!directory_resource_id.empty()) {
1466 // Get the parent resource ID of the entry.
1467 std::string parent_resource_id;
1468 const google_apis::Link* parent_link =
1469 entry->GetLinkByType(Link::LINK_PARENT);
1470 if (parent_link) {
1471 parent_resource_id =
1472 net::UnescapeURLComponent(parent_link->href().ExtractFileName(),
1473 net::UnescapeRule::URL_SPECIAL_CHARS);
1475 if (directory_resource_id != parent_resource_id)
1476 should_exclude = true;
1479 // If |search_query| is set, exclude the entry if it does not contain the
1480 // search query in the title.
1481 if (!should_exclude && !search_query.empty() &&
1482 !EntryMatchWithQuery(*entry, search_query)) {
1483 should_exclude = true;
1486 // If |start_changestamp| is set, exclude the entry if the
1487 // changestamp is older than |largest_changestamp|.
1488 // See https://developers.google.com/google-apps/documents-list/
1489 // #retrieving_all_changes_since_a_given_changestamp
1490 if (start_changestamp > 0 && entry->changestamp() < start_changestamp)
1491 should_exclude = true;
1493 // If the caller requests other list than change list by specifying
1494 // zero-|start_changestamp|, exclude deleted entry from the result.
1495 if (!start_changestamp && entry->deleted())
1496 should_exclude = true;
1498 // The entry matched the criteria for inclusion.
1499 if (!should_exclude)
1500 ++num_entries_matched;
1502 // If |start_offset| is set, exclude the entry if the entry is before the
1503 // start index. <= instead of < as |num_entries_matched| was
1504 // already incremented.
1505 if (start_offset > 0 && num_entries_matched <= start_offset)
1506 should_exclude = true;
1508 if (!should_exclude)
1509 entries.push_back(entry.release());
1512 scoped_ptr<ResourceList> resource_list(new ResourceList);
1513 if (start_changestamp > 0 && start_offset == 0) {
1514 resource_list->set_largest_changestamp(
1515 about_resource_->largest_change_id());
1518 // If |max_results| is set, trim the entries if the number exceeded the max
1519 // results.
1520 if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) {
1521 entries.erase(entries.begin() + max_results, entries.end());
1522 // Adds the next URL.
1523 // Here, we embed information which is needed for continuing the
1524 // GetResourceList request in the next invocation into url query
1525 // parameters.
1526 GURL next_url(base::StringPrintf(
1527 "http://localhost/?start-offset=%d&max-results=%d",
1528 start_offset + max_results,
1529 max_results));
1530 if (start_changestamp > 0) {
1531 next_url = net::AppendOrReplaceQueryParameter(
1532 next_url, "changestamp",
1533 base::Int64ToString(start_changestamp).c_str());
1535 if (!search_query.empty()) {
1536 next_url = net::AppendOrReplaceQueryParameter(
1537 next_url, "q", search_query);
1539 if (!directory_resource_id.empty()) {
1540 next_url = net::AppendOrReplaceQueryParameter(
1541 next_url, "parent", directory_resource_id);
1544 Link* link = new Link;
1545 link->set_type(Link::LINK_NEXT);
1546 link->set_href(next_url);
1547 resource_list->mutable_links()->push_back(link);
1549 resource_list->set_entries(entries.Pass());
1551 if (load_counter)
1552 *load_counter += 1;
1553 base::MessageLoop::current()->PostTask(
1554 FROM_HERE,
1555 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_list)));
1558 GURL FakeDriveService::GetNewUploadSessionUrl() {
1559 return GURL("https://upload_session_url/" +
1560 base::Int64ToString(next_upload_sequence_number_++));
1563 } // namespace drive