1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/drive/fake_drive_service.h"
9 #include "base/file_util.h"
10 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_tokenizer.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "chrome/browser/drive/drive_api_util.h"
20 #include "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
;
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
);
87 base::StringTokenizer
key_value(token
, ":");
88 key_value
.set_quote_chars("\"'");
89 if (!key_value
.GetNext())
91 key
= key_value
.token();
92 if (!key_value
.GetNext())
94 base::TrimString(key_value
.token(), "\"'", &value
);
97 // TODO(peria): Deal with other attributes than title.
98 if (!key
.empty() && key
!= "title")
100 // Search query in the title.
101 if (entry
.title().find(value
) == std::string::npos
)
107 void ScheduleUploadRangeCallback(const UploadRangeCallback
& callback
,
108 int64 start_position
,
110 GDataErrorCode error
,
111 scoped_ptr
<ResourceEntry
> entry
) {
112 base::MessageLoop::current()->PostTask(
115 UploadRangeResponse(error
,
118 base::Passed(&entry
)));
121 void EntryActionCallbackAdapter(
122 const EntryActionCallback
& callback
,
123 GDataErrorCode error
, scoped_ptr
<ResourceEntry
> resource_entry
) {
129 struct FakeDriveService::EntryInfo
{
130 google_apis::ChangeResource change_resource
;
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
;
150 std::string content_type
,
151 int64 content_length
,
152 std::string parent_resource_id
,
153 std::string resource_id
,
156 : content_type(content_type
),
157 content_length(content_length
),
158 parent_resource_id(parent_resource_id
),
159 resource_id(resource_id
),
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),
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
);
221 util::ConvertResourceEntryToFileResource(*resource_entry
));
223 const Link
* share_url
=
224 resource_entry
->GetLinkByType(Link::LINK_SHARE
);
226 new_entry
->share_url
= share_url
->href();
228 entry
->GetString("test$data", &new_entry
->content_data
);
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
);
245 about_resource_
= util::ConvertAccountMetadataToAboutResource(
246 *AccountMetadata::CreateFrom(*value
), GetRootResourceId());
247 if (!about_resource_
)
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();
254 it
->second
->change_resource
.set_change_id(
255 about_resource_
->largest_change_id());
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
));
300 ResourceIdCanonicalizer
FakeDriveService::GetResourceIdCanonicalizer() const {
301 return util::GetIdentityResourceIdCanonicalizer();
304 bool FakeDriveService::HasAccessToken() const {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
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
));
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 {
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,
346 default_max_results_
,
347 &resource_list_load_count_
,
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
,
363 default_max_results_
,
364 &directory_load_count_
,
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
378 std::string(), // no directory resource id,
380 default_max_results_
,
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
,
400 default_max_results_
,
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,
416 default_max_results_
,
417 &change_list_load_count_
,
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());
449 scoped_ptr
<ResourceEntry
> null
;
450 base::MessageLoop::current()->PostTask(
454 base::Passed(&null
)));
455 return CancelCallback();
458 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
460 scoped_ptr
<ResourceEntry
> resource_entry
=
461 util::ConvertChangeResourceToResourceEntry(entry
->change_resource
);
462 base::MessageLoop::current()->PostTask(
464 base::Bind(callback
, HTTP_SUCCESS
, base::Passed(&resource_entry
)));
465 return CancelCallback();
468 scoped_ptr
<ResourceEntry
> null
;
469 base::MessageLoop::current()->PostTask(
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());
483 scoped_ptr
<ResourceEntry
> null
;
484 base::MessageLoop::current()->PostTask(
489 return CancelCallback();
492 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
494 base::MessageLoop::current()->PostTask(
496 base::Bind(callback
, HTTP_SUCCESS
, entry
->share_url
));
497 return CancelCallback();
500 base::MessageLoop::current()->PostTask(
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());
512 scoped_ptr
<AboutResource
> null
;
513 base::MessageLoop::current()->PostTask(
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(
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_
);
535 scoped_ptr
<AppList
> null
;
536 base::MessageLoop::current()->PostTask(
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(
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());
560 base::MessageLoop::current()->PostTask(
561 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
562 return CancelCallback();
565 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
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());
601 base::MessageLoop::current()->PostTask(
602 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
603 return CancelCallback();
606 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
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
;
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());
638 base::MessageLoop::current()->PostTask(
640 base::Bind(download_action_callback
,
643 return CancelCallback();
646 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
648 base::MessageLoopProxy::current()->PostTask(
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(
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(
679 base::Bind(progress_callback
, file_size
/ 2, file_size
));
680 base::MessageLoopProxy::current()->PostTask(
682 base::Bind(progress_callback
, file_size
, file_size
));
684 base::MessageLoopProxy::current()->PostTask(
686 base::Bind(download_action_callback
,
689 return CancelCallback();
692 // Failed to write the content.
693 base::MessageLoopProxy::current()->PostTask(
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());
709 scoped_ptr
<ResourceEntry
> null
;
710 base::MessageLoop::current()->PostTask(
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
);
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(
764 base::Passed(&resource_entry
)));
765 return CancelCallback();
768 scoped_ptr
<ResourceEntry
> null
;
769 base::MessageLoop::current()->PostTask(
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());
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
);
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
);
819 scoped_ptr
<ResourceEntry
> resource_entry
=
820 util::ConvertChangeResourceToResourceEntry(*change
);
821 base::MessageLoop::current()->PostTask(
823 base::Bind(callback
, HTTP_SUCCESS
, base::Passed(&resource_entry
)));
824 return CancelCallback();
827 base::MessageLoop::current()->PostTask(
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());
854 base::MessageLoop::current()->PostTask(
855 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
856 return CancelCallback();
859 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
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());
891 base::MessageLoop::current()->PostTask(
892 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
893 return CancelCallback();
896 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
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());
926 scoped_ptr
<ResourceEntry
> null
;
927 base::MessageLoop::current()->PostTask(
931 base::Passed(&null
)));
932 return CancelCallback();
935 const EntryInfo
* new_entry
= AddNewEntry(kDriveFolderMimeType
,
939 false); // shared_with_me
941 scoped_ptr
<ResourceEntry
> null
;
942 base::MessageLoop::current()->PostTask(
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(
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());
967 base::MessageLoop::current()->PostTask(
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(
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
,
989 base::MessageLoop::current()->PostTask(
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());
1005 base::MessageLoop::current()->PostTask(
1007 base::Bind(callback
, GDATA_NO_CONNECTION
, GURL()));
1008 return CancelCallback();
1011 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
1013 base::MessageLoop::current()->PostTask(
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(
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
1036 base::MessageLoop::current()->PostTask(
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
,
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
);
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
,
1122 session
->parent_resource_id
,
1124 false); // shared_with_me
1126 completion_callback
.Run(HTTP_NOT_FOUND
, scoped_ptr
<ResourceEntry
>());
1127 return CancelCallback();
1130 completion_callback
.Run(
1132 util::ConvertChangeResourceToResourceEntry(new_entry
->change_resource
));
1133 return CancelCallback();
1136 EntryInfo
* entry
= FindEntryByResourceId(session
->resource_id
);
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
);
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
;
1178 error
= google_apis::GDATA_NO_CONNECTION
;
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
;
1185 if (items
->GetDictionary(i
, &item
) && item
->GetString("id", &id
) &&
1187 if (items
->Remove(i
, NULL
))
1188 error
= google_apis::HTTP_NO_CONTENT
;
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
,
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(), '=', '&', ¶meters
)) {
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") {
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());
1268 scoped_ptr
<ResourceEntry
> null
;
1269 base::MessageLoop::current()->PostTask(
1271 base::Bind(callback
,
1272 GDATA_NO_CONNECTION
,
1273 base::Passed(&null
)));
1277 const EntryInfo
* new_entry
= AddNewEntry(content_type
,
1283 scoped_ptr
<ResourceEntry
> null
;
1284 base::MessageLoop::current()->PostTask(
1286 base::Bind(callback
, HTTP_NOT_FOUND
, base::Passed(&null
)));
1290 scoped_ptr
<ResourceEntry
> parsed_entry(
1291 util::ConvertChangeResourceToResourceEntry(new_entry
->change_resource
));
1292 base::MessageLoop::current()->PostTask(
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());
1305 scoped_ptr
<ResourceEntry
> null
;
1306 base::MessageLoop::current()->PostTask(
1308 base::Bind(callback
,
1309 GDATA_NO_CONNECTION
,
1310 base::Passed(&null
)));
1314 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
1316 scoped_ptr
<ResourceEntry
> null
;
1317 base::MessageLoop::current()->PostTask(
1319 base::Bind(callback
, HTTP_NOT_FOUND
, base::Passed(&null
)));
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(
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() ?
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
) {
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
)) {
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
);
1408 scoped_ptr
<ParentReference
> parent(new ParentReference
);
1409 if (parent_resource_id
.empty())
1410 parent
->set_file_id(GetRootResourceId());
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
,
1443 const GetResourceListCallback
& callback
) {
1445 base::MessageLoop::current()->PostTask(
1447 base::Bind(callback
,
1448 GDATA_NO_CONNECTION
,
1449 base::Passed(scoped_ptr
<ResourceList
>())));
1453 // Filter out entries per parameters like |directory_resource_id| and
1455 ScopedVector
<ResourceEntry
> entries
;
1456 int num_entries_matched
= 0;
1457 for (EntryInfoMap::iterator it
= entries_
.begin(); it
!= entries_
.end();
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
);
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
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
1526 GURL
next_url(base::StringPrintf(
1527 "http://localhost/?start-offset=%d&max-results=%d",
1528 start_offset
+ 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());
1553 base::MessageLoop::current()->PostTask(
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