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 : largest_changestamp_(0),
168 published_date_seq_(0),
169 next_upload_sequence_number_(0),
170 default_max_results_(0),
171 resource_id_count_(0),
172 resource_list_load_count_(0),
173 change_list_load_count_(0),
174 directory_load_count_(0),
175 about_resource_load_count_(0),
176 app_list_load_count_(0),
177 blocked_resource_list_load_count_(0),
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 // Load JSON data, which must be a dictionary.
242 scoped_ptr
<base::Value
> value
= test_util::LoadJSONFile(relative_path
);
243 CHECK_EQ(base::Value::TYPE_DICTIONARY
, value
->GetType());
244 account_metadata_value_
.reset(
245 static_cast<base::DictionaryValue
*>(value
.release()));
247 // Update the largest_changestamp_.
248 scoped_ptr
<AccountMetadata
> account_metadata
=
249 AccountMetadata::CreateFrom(*account_metadata_value_
);
250 largest_changestamp_
= account_metadata
->largest_changestamp();
252 // Add the largest changestamp to the existing entries.
253 // This will be used to generate change lists in GetResourceList().
254 for (EntryInfoMap::iterator it
= entries_
.begin(); it
!= entries_
.end(); ++it
)
255 it
->second
->change_resource
.set_change_id(largest_changestamp_
);
257 return account_metadata_value_
;
260 bool FakeDriveService::LoadAppListForDriveApi(
261 const std::string
& relative_path
) {
262 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
264 // Load JSON data, which must be a dictionary.
265 scoped_ptr
<base::Value
> value
= test_util::LoadJSONFile(relative_path
);
266 CHECK_EQ(base::Value::TYPE_DICTIONARY
, value
->GetType());
267 app_info_value_
.reset(
268 static_cast<base::DictionaryValue
*>(value
.release()));
269 return app_info_value_
;
272 void FakeDriveService::SetQuotaValue(int64 used
, int64 total
) {
273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
274 DCHECK(account_metadata_value_
);
276 account_metadata_value_
->SetString("entry.gd$quotaBytesUsed.$t",
277 base::Int64ToString16(used
));
278 account_metadata_value_
->SetString("entry.gd$quotaBytesTotal.$t",
279 base::Int64ToString16(total
));
282 GURL
FakeDriveService::GetFakeLinkUrl(const std::string
& resource_id
) {
283 return GURL("https://fake_server/" + net::EscapePath(resource_id
));
286 void FakeDriveService::Initialize(const std::string
& account_id
) {
287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
290 void FakeDriveService::AddObserver(DriveServiceObserver
* observer
) {
291 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
294 void FakeDriveService::RemoveObserver(DriveServiceObserver
* observer
) {
295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
298 bool FakeDriveService::CanSendRequest() const {
299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
303 ResourceIdCanonicalizer
FakeDriveService::GetResourceIdCanonicalizer() const {
304 return util::GetIdentityResourceIdCanonicalizer();
307 bool FakeDriveService::HasAccessToken() const {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
312 void FakeDriveService::RequestAccessToken(const AuthStatusCallback
& callback
) {
313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
314 DCHECK(!callback
.is_null());
315 callback
.Run(google_apis::HTTP_NOT_MODIFIED
, "fake_access_token");
318 bool FakeDriveService::HasRefreshToken() const {
319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
323 void FakeDriveService::ClearAccessToken() {
324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
327 void FakeDriveService::ClearRefreshToken() {
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
331 std::string
FakeDriveService::GetRootResourceId() const {
335 CancelCallback
FakeDriveService::GetAllResourceList(
336 const GetResourceListCallback
& callback
) {
337 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
338 DCHECK(!callback
.is_null());
340 if (never_return_all_resource_list_
) {
341 ++blocked_resource_list_load_count_
;
342 return CancelCallback();
345 GetResourceListInternal(0, // start changestamp
346 std::string(), // empty search query
347 std::string(), // no directory resource id,
349 default_max_results_
,
350 &resource_list_load_count_
,
352 return CancelCallback();
355 CancelCallback
FakeDriveService::GetResourceListInDirectory(
356 const std::string
& directory_resource_id
,
357 const GetResourceListCallback
& callback
) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
359 DCHECK(!directory_resource_id
.empty());
360 DCHECK(!callback
.is_null());
362 GetResourceListInternal(0, // start changestamp
363 std::string(), // empty search query
364 directory_resource_id
,
366 default_max_results_
,
367 &directory_load_count_
,
369 return CancelCallback();
372 CancelCallback
FakeDriveService::Search(
373 const std::string
& search_query
,
374 const GetResourceListCallback
& callback
) {
375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
376 DCHECK(!search_query
.empty());
377 DCHECK(!callback
.is_null());
379 GetResourceListInternal(0, // start changestamp
381 std::string(), // no directory resource id,
383 default_max_results_
,
386 return CancelCallback();
389 CancelCallback
FakeDriveService::SearchByTitle(
390 const std::string
& title
,
391 const std::string
& directory_resource_id
,
392 const GetResourceListCallback
& callback
) {
393 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
394 DCHECK(!title
.empty());
395 DCHECK(!callback
.is_null());
397 // Note: the search implementation here doesn't support quotation unescape,
398 // so don't escape here.
399 GetResourceListInternal(0, // start changestamp
400 base::StringPrintf("title:'%s'", title
.c_str()),
401 directory_resource_id
,
403 default_max_results_
,
406 return CancelCallback();
409 CancelCallback
FakeDriveService::GetChangeList(
410 int64 start_changestamp
,
411 const GetResourceListCallback
& callback
) {
412 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
413 DCHECK(!callback
.is_null());
415 GetResourceListInternal(start_changestamp
,
416 std::string(), // empty search query
417 std::string(), // no directory resource id,
419 default_max_results_
,
420 &change_list_load_count_
,
422 return CancelCallback();
425 CancelCallback
FakeDriveService::GetRemainingChangeList(
426 const GURL
& next_link
,
427 const GetResourceListCallback
& callback
) {
428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
429 DCHECK(!next_link
.is_empty());
430 DCHECK(!callback
.is_null());
432 return GetRemainingResourceList(next_link
, callback
);
435 CancelCallback
FakeDriveService::GetRemainingFileList(
436 const GURL
& next_link
,
437 const GetResourceListCallback
& callback
) {
438 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
439 DCHECK(!next_link
.is_empty());
440 DCHECK(!callback
.is_null());
442 return GetRemainingResourceList(next_link
, callback
);
445 CancelCallback
FakeDriveService::GetResourceEntry(
446 const std::string
& resource_id
,
447 const GetResourceEntryCallback
& callback
) {
448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
449 DCHECK(!callback
.is_null());
452 scoped_ptr
<ResourceEntry
> null
;
453 base::MessageLoop::current()->PostTask(
457 base::Passed(&null
)));
458 return CancelCallback();
461 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
463 scoped_ptr
<ResourceEntry
> resource_entry
=
464 util::ConvertChangeResourceToResourceEntry(entry
->change_resource
);
465 base::MessageLoop::current()->PostTask(
467 base::Bind(callback
, HTTP_SUCCESS
, base::Passed(&resource_entry
)));
468 return CancelCallback();
471 scoped_ptr
<ResourceEntry
> null
;
472 base::MessageLoop::current()->PostTask(
474 base::Bind(callback
, HTTP_NOT_FOUND
, base::Passed(&null
)));
475 return CancelCallback();
478 CancelCallback
FakeDriveService::GetShareUrl(
479 const std::string
& resource_id
,
480 const GURL
& /* embed_origin */,
481 const GetShareUrlCallback
& callback
) {
482 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
483 DCHECK(!callback
.is_null());
486 scoped_ptr
<ResourceEntry
> null
;
487 base::MessageLoop::current()->PostTask(
492 return CancelCallback();
495 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
497 base::MessageLoop::current()->PostTask(
499 base::Bind(callback
, HTTP_SUCCESS
, entry
->share_url
));
500 return CancelCallback();
503 base::MessageLoop::current()->PostTask(
505 base::Bind(callback
, HTTP_NOT_FOUND
, GURL()));
506 return CancelCallback();
509 CancelCallback
FakeDriveService::GetAboutResource(
510 const AboutResourceCallback
& callback
) {
511 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
512 DCHECK(!callback
.is_null());
515 scoped_ptr
<AboutResource
> null
;
516 base::MessageLoop::current()->PostTask(
519 GDATA_NO_CONNECTION
, base::Passed(&null
)));
520 return CancelCallback();
523 ++about_resource_load_count_
;
524 scoped_ptr
<AboutResource
> about_resource(
525 util::ConvertAccountMetadataToAboutResource(
526 *AccountMetadata::CreateFrom(*account_metadata_value_
),
527 GetRootResourceId()));
528 // Overwrite the change id.
529 about_resource
->set_largest_change_id(largest_changestamp_
);
530 base::MessageLoop::current()->PostTask(
533 HTTP_SUCCESS
, base::Passed(&about_resource
)));
534 return CancelCallback();
537 CancelCallback
FakeDriveService::GetAppList(const AppListCallback
& callback
) {
538 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
539 DCHECK(!callback
.is_null());
540 DCHECK(app_info_value_
);
543 scoped_ptr
<AppList
> null
;
544 base::MessageLoop::current()->PostTask(
548 base::Passed(&null
)));
549 return CancelCallback();
552 ++app_list_load_count_
;
553 scoped_ptr
<AppList
> app_list(AppList::CreateFrom(*app_info_value_
));
554 base::MessageLoop::current()->PostTask(
556 base::Bind(callback
, HTTP_SUCCESS
, base::Passed(&app_list
)));
557 return CancelCallback();
560 CancelCallback
FakeDriveService::DeleteResource(
561 const std::string
& resource_id
,
562 const std::string
& etag
,
563 const EntryActionCallback
& callback
) {
564 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
565 DCHECK(!callback
.is_null());
568 base::MessageLoop::current()->PostTask(
569 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
570 return CancelCallback();
573 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
575 ChangeResource
* change
= &entry
->change_resource
;
576 const FileResource
* file
= change
->file();
577 if (change
->is_deleted()) {
578 base::MessageLoop::current()->PostTask(
579 FROM_HERE
, base::Bind(callback
, HTTP_NOT_FOUND
));
580 return CancelCallback();
583 if (!etag
.empty() && etag
!= file
->etag()) {
584 base::MessageLoop::current()->PostTask(
585 FROM_HERE
, base::Bind(callback
, HTTP_PRECONDITION
));
586 return CancelCallback();
589 change
->set_deleted(true);
590 AddNewChangestamp(change
);
591 change
->set_file(scoped_ptr
<FileResource
>());
592 base::MessageLoop::current()->PostTask(
593 FROM_HERE
, base::Bind(callback
, HTTP_NO_CONTENT
));
594 return CancelCallback();
597 base::MessageLoop::current()->PostTask(
598 FROM_HERE
, base::Bind(callback
, HTTP_NOT_FOUND
));
599 return CancelCallback();
602 CancelCallback
FakeDriveService::TrashResource(
603 const std::string
& resource_id
,
604 const EntryActionCallback
& callback
) {
605 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
606 DCHECK(!callback
.is_null());
609 base::MessageLoop::current()->PostTask(
610 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
611 return CancelCallback();
614 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
616 ChangeResource
* change
= &entry
->change_resource
;
617 FileResource
* file
= change
->mutable_file();
618 GDataErrorCode error
= google_apis::GDATA_OTHER_ERROR
;
619 if (change
->is_deleted() || file
->labels().is_trashed()) {
620 error
= HTTP_NOT_FOUND
;
622 file
->mutable_labels()->set_trashed(true);
623 AddNewChangestamp(change
);
624 error
= HTTP_SUCCESS
;
626 base::MessageLoop::current()->PostTask(
627 FROM_HERE
, base::Bind(callback
, error
));
628 return CancelCallback();
631 base::MessageLoop::current()->PostTask(
632 FROM_HERE
, base::Bind(callback
, HTTP_NOT_FOUND
));
633 return CancelCallback();
636 CancelCallback
FakeDriveService::DownloadFile(
637 const base::FilePath
& local_cache_path
,
638 const std::string
& resource_id
,
639 const DownloadActionCallback
& download_action_callback
,
640 const GetContentCallback
& get_content_callback
,
641 const ProgressCallback
& progress_callback
) {
642 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
643 DCHECK(!download_action_callback
.is_null());
646 base::MessageLoop::current()->PostTask(
648 base::Bind(download_action_callback
,
651 return CancelCallback();
654 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
656 base::MessageLoopProxy::current()->PostTask(
658 base::Bind(download_action_callback
, HTTP_NOT_FOUND
, base::FilePath()));
659 return CancelCallback();
662 const FileResource
* file
= entry
->change_resource
.file();
663 const std::string
& content_data
= entry
->content_data
;
664 int64 file_size
= file
->file_size();
665 DCHECK_EQ(static_cast<size_t>(file_size
), content_data
.size());
667 if (!get_content_callback
.is_null()) {
668 const int64 kBlockSize
= 5;
669 for (int64 i
= 0; i
< file_size
; i
+= kBlockSize
) {
670 const int64 size
= std::min(kBlockSize
, file_size
- i
);
671 scoped_ptr
<std::string
> content_for_callback(
672 new std::string(content_data
.substr(i
, size
)));
673 base::MessageLoopProxy::current()->PostTask(
675 base::Bind(get_content_callback
, HTTP_SUCCESS
,
676 base::Passed(&content_for_callback
)));
680 if (test_util::WriteStringToFile(local_cache_path
, content_data
)) {
681 if (!progress_callback
.is_null()) {
682 // See also the comment in ResumeUpload(). For testing that clients
683 // can handle the case progress_callback is called multiple times,
684 // here we invoke the callback twice.
685 base::MessageLoopProxy::current()->PostTask(
687 base::Bind(progress_callback
, file_size
/ 2, file_size
));
688 base::MessageLoopProxy::current()->PostTask(
690 base::Bind(progress_callback
, file_size
, file_size
));
692 base::MessageLoopProxy::current()->PostTask(
694 base::Bind(download_action_callback
,
697 return CancelCallback();
700 // Failed to write the content.
701 base::MessageLoopProxy::current()->PostTask(
703 base::Bind(download_action_callback
, GDATA_FILE_ERROR
, base::FilePath()));
704 return CancelCallback();
707 CancelCallback
FakeDriveService::CopyResource(
708 const std::string
& resource_id
,
709 const std::string
& in_parent_resource_id
,
710 const std::string
& new_title
,
711 const base::Time
& last_modified
,
712 const GetResourceEntryCallback
& callback
) {
713 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
714 DCHECK(!callback
.is_null());
717 scoped_ptr
<ResourceEntry
> null
;
718 base::MessageLoop::current()->PostTask(
722 base::Passed(&null
)));
723 return CancelCallback();
726 const std::string
& parent_resource_id
= in_parent_resource_id
.empty() ?
727 GetRootResourceId() : in_parent_resource_id
;
729 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
731 // Make a copy and set the new resource ID and the new title.
732 scoped_ptr
<EntryInfo
> copied_entry(new EntryInfo
);
733 copied_entry
->content_data
= entry
->content_data
;
734 copied_entry
->share_url
= entry
->share_url
;
736 // TODO(hashimoto): Implement a proper way to copy FileResource.
737 scoped_ptr
<ResourceEntry
> copied_resource_entry
=
738 util::ConvertChangeResourceToResourceEntry(entry
->change_resource
);
739 copied_entry
->change_resource
.set_file(
740 util::ConvertResourceEntryToFileResource(*copied_resource_entry
));
742 ChangeResource
* new_change
= &copied_entry
->change_resource
;
743 FileResource
* new_file
= new_change
->mutable_file();
744 const std::string new_resource_id
= GetNewResourceId();
745 new_change
->set_file_id(new_resource_id
);
746 new_file
->set_file_id(new_resource_id
);
747 new_file
->set_title(new_title
);
749 scoped_ptr
<ParentReference
> parent(new ParentReference
);
750 parent
->set_file_id(parent_resource_id
);
751 parent
->set_parent_link(GetFakeLinkUrl(parent_resource_id
));
752 parent
->set_is_root(parent_resource_id
== GetRootResourceId());
753 ScopedVector
<ParentReference
> parents
;
754 parents
.push_back(parent
.release());
755 new_file
->set_parents(parents
.Pass());
757 if (!last_modified
.is_null())
758 new_file
->set_modified_date(last_modified
);
760 AddNewChangestamp(new_change
);
761 UpdateETag(new_file
);
763 scoped_ptr
<ResourceEntry
> resource_entry
=
764 util::ConvertChangeResourceToResourceEntry(*new_change
);
765 // Add the new entry to the map.
766 entries_
[new_resource_id
] = copied_entry
.release();
768 base::MessageLoop::current()->PostTask(
772 base::Passed(&resource_entry
)));
773 return CancelCallback();
776 scoped_ptr
<ResourceEntry
> null
;
777 base::MessageLoop::current()->PostTask(
779 base::Bind(callback
, HTTP_NOT_FOUND
, base::Passed(&null
)));
780 return CancelCallback();
783 CancelCallback
FakeDriveService::UpdateResource(
784 const std::string
& resource_id
,
785 const std::string
& parent_resource_id
,
786 const std::string
& new_title
,
787 const base::Time
& last_modified
,
788 const base::Time
& last_viewed_by_me
,
789 const google_apis::GetResourceEntryCallback
& callback
) {
790 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
791 DCHECK(!callback
.is_null());
794 base::MessageLoop::current()->PostTask(
795 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
,
796 base::Passed(scoped_ptr
<ResourceEntry
>())));
797 return CancelCallback();
800 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
802 ChangeResource
* change
= &entry
->change_resource
;
803 FileResource
* file
= change
->mutable_file();
804 file
->set_title(new_title
);
806 // Set parent if necessary.
807 if (!parent_resource_id
.empty()) {
808 scoped_ptr
<ParentReference
> parent(new ParentReference
);
809 parent
->set_file_id(parent_resource_id
);
810 parent
->set_parent_link(GetFakeLinkUrl(parent_resource_id
));
811 parent
->set_is_root(parent_resource_id
== GetRootResourceId());
813 ScopedVector
<ParentReference
> parents
;
814 parents
.push_back(parent
.release());
815 file
->set_parents(parents
.Pass());
818 if (!last_modified
.is_null())
819 file
->set_modified_date(last_modified
);
821 if (!last_viewed_by_me
.is_null())
822 file
->set_last_viewed_by_me_date(last_viewed_by_me
);
824 AddNewChangestamp(change
);
827 scoped_ptr
<ResourceEntry
> resource_entry
=
828 util::ConvertChangeResourceToResourceEntry(*change
);
829 base::MessageLoop::current()->PostTask(
831 base::Bind(callback
, HTTP_SUCCESS
, base::Passed(&resource_entry
)));
832 return CancelCallback();
835 base::MessageLoop::current()->PostTask(
837 base::Bind(callback
, HTTP_NOT_FOUND
,
838 base::Passed(scoped_ptr
<ResourceEntry
>())));
839 return CancelCallback();
842 CancelCallback
FakeDriveService::RenameResource(
843 const std::string
& resource_id
,
844 const std::string
& new_title
,
845 const EntryActionCallback
& callback
) {
846 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
847 DCHECK(!callback
.is_null());
849 return UpdateResource(
850 resource_id
, std::string(), new_title
, base::Time(), base::Time(),
851 base::Bind(&EntryActionCallbackAdapter
, callback
));
854 CancelCallback
FakeDriveService::AddResourceToDirectory(
855 const std::string
& parent_resource_id
,
856 const std::string
& resource_id
,
857 const EntryActionCallback
& callback
) {
858 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
859 DCHECK(!callback
.is_null());
862 base::MessageLoop::current()->PostTask(
863 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
864 return CancelCallback();
867 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
869 ChangeResource
* change
= &entry
->change_resource
;
870 // On the real Drive server, resources do not necessary shape a tree
871 // structure. That is, each resource can have multiple parent.
872 // We mimic the behavior here; AddResourceToDirectoy just adds
873 // one more parent, not overwriting old ones.
874 scoped_ptr
<ParentReference
> parent(new ParentReference
);
875 parent
->set_file_id(parent_resource_id
);
876 parent
->set_parent_link(GetFakeLinkUrl(parent_resource_id
));
877 parent
->set_is_root(parent_resource_id
== GetRootResourceId());
878 change
->mutable_file()->mutable_parents()->push_back(parent
.release());
880 AddNewChangestamp(change
);
881 base::MessageLoop::current()->PostTask(
882 FROM_HERE
, base::Bind(callback
, HTTP_SUCCESS
));
883 return CancelCallback();
886 base::MessageLoop::current()->PostTask(
887 FROM_HERE
, base::Bind(callback
, HTTP_NOT_FOUND
));
888 return CancelCallback();
891 CancelCallback
FakeDriveService::RemoveResourceFromDirectory(
892 const std::string
& parent_resource_id
,
893 const std::string
& resource_id
,
894 const EntryActionCallback
& callback
) {
895 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
896 DCHECK(!callback
.is_null());
899 base::MessageLoop::current()->PostTask(
900 FROM_HERE
, base::Bind(callback
, GDATA_NO_CONNECTION
));
901 return CancelCallback();
904 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
906 ChangeResource
* change
= &entry
->change_resource
;
907 FileResource
* file
= change
->mutable_file();
908 ScopedVector
<ParentReference
>* parents
= file
->mutable_parents();
909 for (size_t i
= 0; i
< parents
->size(); ++i
) {
910 if ((*parents
)[i
]->file_id() == parent_resource_id
) {
911 parents
->erase(parents
->begin() + i
);
912 AddNewChangestamp(change
);
913 base::MessageLoop::current()->PostTask(
914 FROM_HERE
, base::Bind(callback
, HTTP_NO_CONTENT
));
915 return CancelCallback();
920 base::MessageLoop::current()->PostTask(
921 FROM_HERE
, base::Bind(callback
, HTTP_NOT_FOUND
));
922 return CancelCallback();
925 CancelCallback
FakeDriveService::AddNewDirectory(
926 const std::string
& parent_resource_id
,
927 const std::string
& directory_title
,
928 const GetResourceEntryCallback
& callback
) {
929 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
930 DCHECK(!callback
.is_null());
933 scoped_ptr
<ResourceEntry
> null
;
934 base::MessageLoop::current()->PostTask(
938 base::Passed(&null
)));
939 return CancelCallback();
942 const EntryInfo
* new_entry
= AddNewEntry(kDriveFolderMimeType
,
946 false); // shared_with_me
948 scoped_ptr
<ResourceEntry
> null
;
949 base::MessageLoop::current()->PostTask(
951 base::Bind(callback
, HTTP_NOT_FOUND
, base::Passed(&null
)));
952 return CancelCallback();
955 scoped_ptr
<ResourceEntry
> parsed_entry(
956 util::ConvertChangeResourceToResourceEntry(new_entry
->change_resource
));
957 base::MessageLoop::current()->PostTask(
959 base::Bind(callback
, HTTP_CREATED
, base::Passed(&parsed_entry
)));
960 return CancelCallback();
963 CancelCallback
FakeDriveService::InitiateUploadNewFile(
964 const std::string
& content_type
,
965 int64 content_length
,
966 const std::string
& parent_resource_id
,
967 const std::string
& title
,
968 const InitiateUploadCallback
& callback
) {
969 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
970 DCHECK(!callback
.is_null());
973 base::MessageLoop::current()->PostTask(
975 base::Bind(callback
, GDATA_NO_CONNECTION
, GURL()));
976 return CancelCallback();
979 if (parent_resource_id
!= GetRootResourceId() &&
980 !entries_
.count(parent_resource_id
)) {
981 base::MessageLoop::current()->PostTask(
983 base::Bind(callback
, HTTP_NOT_FOUND
, GURL()));
984 return CancelCallback();
987 GURL session_url
= GetNewUploadSessionUrl();
988 upload_sessions_
[session_url
] =
989 UploadSession(content_type
, content_length
,
995 base::MessageLoop::current()->PostTask(
997 base::Bind(callback
, HTTP_SUCCESS
, session_url
));
998 return CancelCallback();
1001 CancelCallback
FakeDriveService::InitiateUploadExistingFile(
1002 const std::string
& content_type
,
1003 int64 content_length
,
1004 const std::string
& resource_id
,
1005 const std::string
& etag
,
1006 const InitiateUploadCallback
& callback
) {
1007 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1008 DCHECK(!callback
.is_null());
1011 base::MessageLoop::current()->PostTask(
1013 base::Bind(callback
, GDATA_NO_CONNECTION
, GURL()));
1014 return CancelCallback();
1017 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
1019 base::MessageLoop::current()->PostTask(
1021 base::Bind(callback
, HTTP_NOT_FOUND
, GURL()));
1022 return CancelCallback();
1025 const FileResource
* file
= entry
->change_resource
.file();
1026 if (!etag
.empty() && etag
!= file
->etag()) {
1027 base::MessageLoop::current()->PostTask(
1029 base::Bind(callback
, HTTP_PRECONDITION
, GURL()));
1030 return CancelCallback();
1033 GURL session_url
= GetNewUploadSessionUrl();
1034 upload_sessions_
[session_url
] =
1035 UploadSession(content_type
, content_length
,
1036 "", // parent_resource_id
1041 base::MessageLoop::current()->PostTask(
1043 base::Bind(callback
, HTTP_SUCCESS
, session_url
));
1044 return CancelCallback();
1047 CancelCallback
FakeDriveService::GetUploadStatus(
1048 const GURL
& upload_url
,
1049 int64 content_length
,
1050 const UploadRangeCallback
& callback
) {
1051 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1052 DCHECK(!callback
.is_null());
1053 return CancelCallback();
1056 CancelCallback
FakeDriveService::ResumeUpload(
1057 const GURL
& upload_url
,
1058 int64 start_position
,
1060 int64 content_length
,
1061 const std::string
& content_type
,
1062 const base::FilePath
& local_file_path
,
1063 const UploadRangeCallback
& callback
,
1064 const ProgressCallback
& progress_callback
) {
1065 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1066 DCHECK(!callback
.is_null());
1068 GetResourceEntryCallback completion_callback
1069 = base::Bind(&ScheduleUploadRangeCallback
,
1070 callback
, start_position
, end_position
);
1073 completion_callback
.Run(GDATA_NO_CONNECTION
, scoped_ptr
<ResourceEntry
>());
1074 return CancelCallback();
1077 if (!upload_sessions_
.count(upload_url
)) {
1078 completion_callback
.Run(HTTP_NOT_FOUND
, scoped_ptr
<ResourceEntry
>());
1079 return CancelCallback();
1082 UploadSession
* session
= &upload_sessions_
[upload_url
];
1084 // Chunks are required to be sent in such a ways that they fill from the start
1085 // of the not-yet-uploaded part with no gaps nor overlaps.
1086 if (session
->uploaded_size
!= start_position
) {
1087 completion_callback
.Run(HTTP_BAD_REQUEST
, scoped_ptr
<ResourceEntry
>());
1088 return CancelCallback();
1091 if (!progress_callback
.is_null()) {
1092 // In the real GDataWapi/Drive DriveService, progress is reported in
1093 // nondeterministic timing. In this fake implementation, we choose to call
1094 // it twice per one ResumeUpload. This is for making sure that client code
1095 // works fine even if the callback is invoked more than once; it is the
1096 // crucial difference of the progress callback from others.
1097 // Note that progress is notified in the relative offset in each chunk.
1098 const int64 chunk_size
= end_position
- start_position
;
1099 base::MessageLoop::current()->PostTask(
1100 FROM_HERE
, base::Bind(progress_callback
, chunk_size
/ 2, chunk_size
));
1101 base::MessageLoop::current()->PostTask(
1102 FROM_HERE
, base::Bind(progress_callback
, chunk_size
, chunk_size
));
1105 if (content_length
!= end_position
) {
1106 session
->uploaded_size
= end_position
;
1107 completion_callback
.Run(HTTP_RESUME_INCOMPLETE
,
1108 scoped_ptr
<ResourceEntry
>());
1109 return CancelCallback();
1112 std::string content_data
;
1113 if (!base::ReadFileToString(local_file_path
, &content_data
)) {
1114 session
->uploaded_size
= end_position
;
1115 completion_callback
.Run(GDATA_FILE_ERROR
, scoped_ptr
<ResourceEntry
>());
1116 return CancelCallback();
1118 session
->uploaded_size
= end_position
;
1120 // |resource_id| is empty if the upload is for new file.
1121 if (session
->resource_id
.empty()) {
1122 DCHECK(!session
->parent_resource_id
.empty());
1123 DCHECK(!session
->title
.empty());
1124 const EntryInfo
* new_entry
= AddNewEntry(
1125 session
->content_type
,
1127 session
->parent_resource_id
,
1129 false); // shared_with_me
1131 completion_callback
.Run(HTTP_NOT_FOUND
, scoped_ptr
<ResourceEntry
>());
1132 return CancelCallback();
1135 completion_callback
.Run(
1137 util::ConvertChangeResourceToResourceEntry(new_entry
->change_resource
));
1138 return CancelCallback();
1141 EntryInfo
* entry
= FindEntryByResourceId(session
->resource_id
);
1143 completion_callback
.Run(HTTP_NOT_FOUND
, scoped_ptr
<ResourceEntry
>());
1144 return CancelCallback();
1147 ChangeResource
* change
= &entry
->change_resource
;
1148 FileResource
* file
= change
->mutable_file();
1149 if (file
->etag().empty() || session
->etag
!= file
->etag()) {
1150 completion_callback
.Run(HTTP_PRECONDITION
, scoped_ptr
<ResourceEntry
>());
1151 return CancelCallback();
1154 file
->set_md5_checksum(base::MD5String(content_data
));
1155 entry
->content_data
= content_data
;
1156 file
->set_file_size(end_position
);
1157 AddNewChangestamp(change
);
1160 completion_callback
.Run(HTTP_SUCCESS
,
1161 util::ConvertChangeResourceToResourceEntry(*change
));
1162 return CancelCallback();
1165 CancelCallback
FakeDriveService::AuthorizeApp(
1166 const std::string
& resource_id
,
1167 const std::string
& app_id
,
1168 const AuthorizeAppCallback
& callback
) {
1169 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1170 DCHECK(!callback
.is_null());
1171 return CancelCallback();
1174 CancelCallback
FakeDriveService::UninstallApp(
1175 const std::string
& app_id
,
1176 const google_apis::EntryActionCallback
& callback
) {
1177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1178 DCHECK(!callback
.is_null());
1180 // Find app_id from app_info_value_ and delete.
1181 google_apis::GDataErrorCode error
= google_apis::HTTP_NOT_FOUND
;
1183 error
= google_apis::GDATA_NO_CONNECTION
;
1185 base::ListValue
* items
= NULL
;
1186 if (app_info_value_
->GetList("items", &items
)) {
1187 for (size_t i
= 0; i
< items
->GetSize(); ++i
) {
1188 base::DictionaryValue
* item
= NULL
;
1190 if (items
->GetDictionary(i
, &item
) && item
->GetString("id", &id
) &&
1192 if (items
->Remove(i
, NULL
))
1193 error
= google_apis::HTTP_NO_CONTENT
;
1200 base::MessageLoop::current()->PostTask(FROM_HERE
,
1201 base::Bind(callback
, error
));
1202 return CancelCallback();
1205 CancelCallback
FakeDriveService::GetResourceListInDirectoryByWapi(
1206 const std::string
& directory_resource_id
,
1207 const google_apis::GetResourceListCallback
& callback
) {
1208 return GetResourceListInDirectory(
1209 directory_resource_id
== util::kWapiRootDirectoryResourceId
?
1210 GetRootResourceId() :
1211 directory_resource_id
,
1215 CancelCallback
FakeDriveService::GetRemainingResourceList(
1216 const GURL
& next_link
,
1217 const google_apis::GetResourceListCallback
& callback
) {
1218 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1219 DCHECK(!next_link
.is_empty());
1220 DCHECK(!callback
.is_null());
1222 // "changestamp", "q", "parent" and "start-offset" are parameters to
1223 // implement "paging" of the result on FakeDriveService.
1224 // The URL should be the one filled in GetResourceListInternal of the
1225 // previous method invocation, so it should start with "http://localhost/?".
1226 // See also GetResourceListInternal.
1227 DCHECK_EQ(next_link
.host(), "localhost");
1228 DCHECK_EQ(next_link
.path(), "/");
1230 int64 start_changestamp
= 0;
1231 std::string search_query
;
1232 std::string directory_resource_id
;
1233 int start_offset
= 0;
1234 int max_results
= default_max_results_
;
1235 std::vector
<std::pair
<std::string
, std::string
> > parameters
;
1236 if (base::SplitStringIntoKeyValuePairs(
1237 next_link
.query(), '=', '&', ¶meters
)) {
1238 for (size_t i
= 0; i
< parameters
.size(); ++i
) {
1239 if (parameters
[i
].first
== "changestamp") {
1240 base::StringToInt64(parameters
[i
].second
, &start_changestamp
);
1241 } else if (parameters
[i
].first
== "q") {
1243 net::UnescapeURLComponent(parameters
[i
].second
,
1244 net::UnescapeRule::URL_SPECIAL_CHARS
);
1245 } else if (parameters
[i
].first
== "parent") {
1246 directory_resource_id
=
1247 net::UnescapeURLComponent(parameters
[i
].second
,
1248 net::UnescapeRule::URL_SPECIAL_CHARS
);
1249 } else if (parameters
[i
].first
== "start-offset") {
1250 base::StringToInt(parameters
[i
].second
, &start_offset
);
1251 } else if (parameters
[i
].first
== "max-results") {
1252 base::StringToInt(parameters
[i
].second
, &max_results
);
1257 GetResourceListInternal(
1258 start_changestamp
, search_query
, directory_resource_id
,
1259 start_offset
, max_results
, NULL
, callback
);
1260 return CancelCallback();
1263 void FakeDriveService::AddNewFile(const std::string
& content_type
,
1264 const std::string
& content_data
,
1265 const std::string
& parent_resource_id
,
1266 const std::string
& title
,
1267 bool shared_with_me
,
1268 const GetResourceEntryCallback
& callback
) {
1269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1270 DCHECK(!callback
.is_null());
1273 scoped_ptr
<ResourceEntry
> null
;
1274 base::MessageLoop::current()->PostTask(
1276 base::Bind(callback
,
1277 GDATA_NO_CONNECTION
,
1278 base::Passed(&null
)));
1282 const EntryInfo
* new_entry
= AddNewEntry(content_type
,
1288 scoped_ptr
<ResourceEntry
> null
;
1289 base::MessageLoop::current()->PostTask(
1291 base::Bind(callback
, HTTP_NOT_FOUND
, base::Passed(&null
)));
1295 scoped_ptr
<ResourceEntry
> parsed_entry(
1296 util::ConvertChangeResourceToResourceEntry(new_entry
->change_resource
));
1297 base::MessageLoop::current()->PostTask(
1299 base::Bind(callback
, HTTP_CREATED
, base::Passed(&parsed_entry
)));
1302 void FakeDriveService::SetLastModifiedTime(
1303 const std::string
& resource_id
,
1304 const base::Time
& last_modified_time
,
1305 const GetResourceEntryCallback
& callback
) {
1306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1307 DCHECK(!callback
.is_null());
1310 scoped_ptr
<ResourceEntry
> null
;
1311 base::MessageLoop::current()->PostTask(
1313 base::Bind(callback
,
1314 GDATA_NO_CONNECTION
,
1315 base::Passed(&null
)));
1319 EntryInfo
* entry
= FindEntryByResourceId(resource_id
);
1321 scoped_ptr
<ResourceEntry
> null
;
1322 base::MessageLoop::current()->PostTask(
1324 base::Bind(callback
, HTTP_NOT_FOUND
, base::Passed(&null
)));
1328 ChangeResource
* change
= &entry
->change_resource
;
1329 FileResource
* file
= change
->mutable_file();
1330 file
->set_modified_date(last_modified_time
);
1332 scoped_ptr
<ResourceEntry
> parsed_entry(
1333 util::ConvertChangeResourceToResourceEntry(*change
));
1334 base::MessageLoop::current()->PostTask(
1336 base::Bind(callback
, HTTP_SUCCESS
, base::Passed(&parsed_entry
)));
1339 FakeDriveService::EntryInfo
* FakeDriveService::FindEntryByResourceId(
1340 const std::string
& resource_id
) {
1341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1343 EntryInfoMap::iterator it
= entries_
.find(resource_id
);
1344 // Deleted entries don't have FileResource.
1345 return it
!= entries_
.end() && it
->second
->change_resource
.file() ?
1349 std::string
FakeDriveService::GetNewResourceId() {
1350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1352 ++resource_id_count_
;
1353 return base::StringPrintf("resource_id_%d", resource_id_count_
);
1356 void FakeDriveService::UpdateETag(google_apis::FileResource
* file
) {
1357 file
->set_etag("etag_" + base::Int64ToString(largest_changestamp_
));
1360 void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource
* change
) {
1361 ++largest_changestamp_
;
1362 change
->set_change_id(largest_changestamp_
);
1365 const FakeDriveService::EntryInfo
* FakeDriveService::AddNewEntry(
1366 const std::string
& content_type
,
1367 const std::string
& content_data
,
1368 const std::string
& parent_resource_id
,
1369 const std::string
& title
,
1370 bool shared_with_me
) {
1371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1373 if (!parent_resource_id
.empty() &&
1374 parent_resource_id
!= GetRootResourceId() &&
1375 !entries_
.count(parent_resource_id
)) {
1379 std::string resource_id
= GetNewResourceId();
1380 GURL upload_url
= GURL("https://xxx/upload/" + resource_id
);
1382 scoped_ptr
<EntryInfo
> new_entry(new EntryInfo
);
1383 ChangeResource
* new_change
= &new_entry
->change_resource
;
1384 FileResource
* new_file
= new FileResource
;
1385 new_change
->set_file(make_scoped_ptr(new_file
));
1387 // Set the resource ID and the title
1388 new_change
->set_file_id(resource_id
);
1389 new_file
->set_file_id(resource_id
);
1390 new_file
->set_title(title
);
1391 // Set the contents, size and MD5 for a file.
1392 if (content_type
!= kDriveFolderMimeType
) {
1393 new_entry
->content_data
= content_data
;
1394 new_file
->set_file_size(content_data
.size());
1395 new_file
->set_md5_checksum(base::MD5String(content_data
));
1398 if (shared_with_me
) {
1399 // Set current time to mark the file as shared_with_me.
1400 new_file
->set_shared_with_me_date(base::Time::Now());
1403 std::string escaped_resource_id
= net::EscapePath(resource_id
);
1405 // Set download URL and mime type.
1406 new_file
->set_download_url(
1407 GURL("https://xxx/content/" + escaped_resource_id
));
1408 new_file
->set_mime_type(content_type
);
1411 scoped_ptr
<ParentReference
> parent(new ParentReference
);
1412 if (parent_resource_id
.empty())
1413 parent
->set_file_id(GetRootResourceId());
1415 parent
->set_file_id(parent_resource_id
);
1416 parent
->set_parent_link(GetFakeLinkUrl(parent
->file_id()));
1417 parent
->set_is_root(parent
->file_id() == GetRootResourceId());
1418 ScopedVector
<ParentReference
> parents
;
1419 parents
.push_back(parent
.release());
1420 new_file
->set_parents(parents
.Pass());
1422 new_file
->set_self_link(GURL("https://xxx/edit/" + escaped_resource_id
));
1424 new_entry
->share_url
= net::AppendOrReplaceQueryParameter(
1425 share_url_base_
, "name", title
);
1427 AddNewChangestamp(new_change
);
1428 UpdateETag(new_file
);
1430 base::Time published_date
=
1431 base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_
);
1432 new_file
->set_created_date(published_date
);
1434 EntryInfo
* raw_new_entry
= new_entry
.release();
1435 entries_
[resource_id
] = raw_new_entry
;
1436 return raw_new_entry
;
1439 void FakeDriveService::GetResourceListInternal(
1440 int64 start_changestamp
,
1441 const std::string
& search_query
,
1442 const std::string
& directory_resource_id
,
1446 const GetResourceListCallback
& callback
) {
1448 base::MessageLoop::current()->PostTask(
1450 base::Bind(callback
,
1451 GDATA_NO_CONNECTION
,
1452 base::Passed(scoped_ptr
<ResourceList
>())));
1456 // Filter out entries per parameters like |directory_resource_id| and
1458 ScopedVector
<ResourceEntry
> entries
;
1459 int num_entries_matched
= 0;
1460 for (EntryInfoMap::iterator it
= entries_
.begin(); it
!= entries_
.end();
1462 scoped_ptr
<ResourceEntry
> entry
=
1463 util::ConvertChangeResourceToResourceEntry(it
->second
->change_resource
);
1464 bool should_exclude
= false;
1466 // If |directory_resource_id| is set, exclude the entry if it's not in
1467 // the target directory.
1468 if (!directory_resource_id
.empty()) {
1469 // Get the parent resource ID of the entry.
1470 std::string parent_resource_id
;
1471 const google_apis::Link
* parent_link
=
1472 entry
->GetLinkByType(Link::LINK_PARENT
);
1474 parent_resource_id
=
1475 net::UnescapeURLComponent(parent_link
->href().ExtractFileName(),
1476 net::UnescapeRule::URL_SPECIAL_CHARS
);
1478 if (directory_resource_id
!= parent_resource_id
)
1479 should_exclude
= true;
1482 // If |search_query| is set, exclude the entry if it does not contain the
1483 // search query in the title.
1484 if (!should_exclude
&& !search_query
.empty() &&
1485 !EntryMatchWithQuery(*entry
, search_query
)) {
1486 should_exclude
= true;
1489 // If |start_changestamp| is set, exclude the entry if the
1490 // changestamp is older than |largest_changestamp|.
1491 // See https://developers.google.com/google-apps/documents-list/
1492 // #retrieving_all_changes_since_a_given_changestamp
1493 if (start_changestamp
> 0 && entry
->changestamp() < start_changestamp
)
1494 should_exclude
= true;
1496 // If the caller requests other list than change list by specifying
1497 // zero-|start_changestamp|, exclude deleted entry from the result.
1498 if (!start_changestamp
&& entry
->deleted())
1499 should_exclude
= true;
1501 // The entry matched the criteria for inclusion.
1502 if (!should_exclude
)
1503 ++num_entries_matched
;
1505 // If |start_offset| is set, exclude the entry if the entry is before the
1506 // start index. <= instead of < as |num_entries_matched| was
1507 // already incremented.
1508 if (start_offset
> 0 && num_entries_matched
<= start_offset
)
1509 should_exclude
= true;
1511 if (!should_exclude
)
1512 entries
.push_back(entry
.release());
1515 scoped_ptr
<ResourceList
> resource_list(new ResourceList
);
1516 if (start_changestamp
> 0 && start_offset
== 0)
1517 resource_list
->set_largest_changestamp(largest_changestamp_
);
1519 // If |max_results| is set, trim the entries if the number exceeded the max
1521 if (max_results
> 0 && entries
.size() > static_cast<size_t>(max_results
)) {
1522 entries
.erase(entries
.begin() + max_results
, entries
.end());
1523 // Adds the next URL.
1524 // Here, we embed information which is needed for continuing the
1525 // GetResourceList request in the next invocation into url query
1527 GURL
next_url(base::StringPrintf(
1528 "http://localhost/?start-offset=%d&max-results=%d",
1529 start_offset
+ max_results
,
1531 if (start_changestamp
> 0) {
1532 next_url
= net::AppendOrReplaceQueryParameter(
1533 next_url
, "changestamp",
1534 base::Int64ToString(start_changestamp
).c_str());
1536 if (!search_query
.empty()) {
1537 next_url
= net::AppendOrReplaceQueryParameter(
1538 next_url
, "q", search_query
);
1540 if (!directory_resource_id
.empty()) {
1541 next_url
= net::AppendOrReplaceQueryParameter(
1542 next_url
, "parent", directory_resource_id
);
1545 Link
* link
= new Link
;
1546 link
->set_type(Link::LINK_NEXT
);
1547 link
->set_href(next_url
);
1548 resource_list
->mutable_links()->push_back(link
);
1550 resource_list
->set_entries(entries
.Pass());
1554 base::MessageLoop::current()->PostTask(
1556 base::Bind(callback
, HTTP_SUCCESS
, base::Passed(&resource_list
)));
1559 GURL
FakeDriveService::GetNewUploadSessionUrl() {
1560 return GURL("https://upload_session_url/" +
1561 base::Int64ToString(next_upload_sequence_number_
++));
1564 } // namespace drive