Fix build break
[chromium-blink-merge.git] / chrome / browser / google_apis / fake_drive_service.cc
blob03ece2cd3075f56f503d6bd325905341e1b36612
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/google_apis/fake_drive_service.h"
7 #include <string>
9 #include "base/file_util.h"
10 #include "base/logging.h"
11 #include "base/message_loop.h"
12 #include "base/string_util.h"
13 #include "base/stringprintf.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_tokenizer.h"
17 #include "base/utf_string_conversions.h"
18 #include "chrome/browser/google_apis/drive_api_parser.h"
19 #include "chrome/browser/google_apis/gdata_wapi_parser.h"
20 #include "chrome/browser/google_apis/test_util.h"
21 #include "chrome/browser/google_apis/time_util.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "net/base/escape.h"
24 #include "net/base/url_util.h"
26 using content::BrowserThread;
28 namespace google_apis {
29 namespace {
31 // Rel property of upload link in the entries dictionary value.
32 const char kUploadUrlRel[] =
33 "http://schemas.google.com/g/2005#resumable-create-media";
35 // Returns true if a resource entry matches with the search query.
36 // Supports queries consist of following format.
37 // - Phrases quoted by double/single quotes
38 // - AND search for multiple words/phrases segmented by space
39 // - Limited attribute search. Only "title:" is supported.
40 bool EntryMatchWithQuery(const ResourceEntry& entry,
41 const std::string& query) {
42 base::StringTokenizer tokenizer(query, " ");
43 tokenizer.set_quote_chars("\"'");
44 while (tokenizer.GetNext()) {
45 std::string key, value;
46 const std::string& token = tokenizer.token();
47 if (token.find(':') == std::string::npos) {
48 TrimString(token, "\"'", &value);
49 } else {
50 base::StringTokenizer key_value(token, ":");
51 key_value.set_quote_chars("\"'");
52 if (!key_value.GetNext())
53 return false;
54 key = key_value.token();
55 if (!key_value.GetNext())
56 return false;
57 TrimString(key_value.token(), "\"'", &value);
60 // TODO(peria): Deal with other attributes than title.
61 if (!key.empty() && key != "title")
62 return false;
63 // Search query in the title.
64 if (entry.title().find(value) == std::string::npos)
65 return false;
67 return true;
70 // Gets the upload URL from the given entry. Returns an empty URL if not
71 // found.
72 GURL GetUploadUrl(const base::DictionaryValue& entry) {
73 std::string upload_url;
74 const base::ListValue* links = NULL;
75 if (entry.GetList("link", &links) && links) {
76 for (size_t link_index = 0;
77 link_index < links->GetSize();
78 ++link_index) {
79 const base::DictionaryValue* link = NULL;
80 std::string rel;
81 if (links->GetDictionary(link_index, &link) &&
82 link && link->GetString("rel", &rel) &&
83 rel == kUploadUrlRel &&
84 link->GetString("href", &upload_url)) {
85 break;
89 return GURL(upload_url);
92 } // namespace
94 FakeDriveService::FakeDriveService()
95 : largest_changestamp_(0),
96 default_max_results_(0),
97 resource_id_count_(0),
98 resource_list_load_count_(0),
99 account_metadata_load_count_(0),
100 about_resource_load_count_(0),
101 offline_(false) {
102 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
105 FakeDriveService::~FakeDriveService() {
106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
109 bool FakeDriveService::LoadResourceListForWapi(
110 const std::string& relative_path) {
111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
112 scoped_ptr<Value> raw_value = test_util::LoadJSONFile(relative_path);
113 base::DictionaryValue* as_dict = NULL;
114 base::Value* feed = NULL;
115 base::DictionaryValue* feed_as_dict = NULL;
117 // Extract the "feed" from the raw value and take the ownership.
118 // Note that Remove() transfers the ownership to |feed|.
119 if (raw_value->GetAsDictionary(&as_dict) &&
120 as_dict->Remove("feed", &feed) &&
121 feed->GetAsDictionary(&feed_as_dict)) {
122 resource_list_value_.reset(feed_as_dict);
125 return resource_list_value_;
128 bool FakeDriveService::LoadAccountMetadataForWapi(
129 const std::string& relative_path) {
130 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
131 account_metadata_value_ = test_util::LoadJSONFile(relative_path);
133 // Update the largest_changestamp_.
134 scoped_ptr<AccountMetadata> account_metadata =
135 AccountMetadata::CreateFrom(*account_metadata_value_);
136 largest_changestamp_ = account_metadata->largest_changestamp();
138 // Add the largest changestamp to the existing entries.
139 // This will be used to generate change lists in GetResourceList().
140 if (resource_list_value_) {
141 base::DictionaryValue* resource_list_dict = NULL;
142 base::ListValue* entries = NULL;
143 if (resource_list_value_->GetAsDictionary(&resource_list_dict) &&
144 resource_list_dict->GetList("entry", &entries)) {
145 for (size_t i = 0; i < entries->GetSize(); ++i) {
146 base::DictionaryValue* entry = NULL;
147 if (entries->GetDictionary(i, &entry)) {
148 entry->SetString("docs$changestamp.value",
149 base::Int64ToString(largest_changestamp_));
155 return account_metadata_value_;
158 bool FakeDriveService::LoadAppListForDriveApi(
159 const std::string& relative_path) {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
161 app_info_value_ = test_util::LoadJSONFile(relative_path);
162 return app_info_value_;
165 GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
166 return GURL("https://fake_server/" + resource_id);
169 void FakeDriveService::Initialize(Profile* profile) {
170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
173 void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
174 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
177 void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
181 bool FakeDriveService::CanStartOperation() const {
182 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
183 return true;
186 void FakeDriveService::CancelAll() {
187 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190 bool FakeDriveService::CancelForFilePath(const base::FilePath& file_path) {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192 last_cancelled_file_ = file_path;
193 return true;
196 OperationProgressStatusList FakeDriveService::GetProgressStatusList() const {
197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
198 return OperationProgressStatusList();
201 bool FakeDriveService::HasAccessToken() const {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203 return true;
206 bool FakeDriveService::HasRefreshToken() const {
207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208 return true;
211 void FakeDriveService::ClearAccessToken() {
212 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
215 void FakeDriveService::ClearRefreshToken() {
216 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
219 std::string FakeDriveService::GetRootResourceId() const {
220 return "fake_root";
223 void FakeDriveService::GetAllResourceList(
224 const GetResourceListCallback& callback) {
225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
226 DCHECK(!callback.is_null());
228 GetResourceListInternal(0, // start changestamp
229 std::string(), // empty search query
230 std::string(), // no directory resource id,
231 0, // start offset
232 default_max_results_,
233 callback);
236 void FakeDriveService::GetResourceListInDirectory(
237 const std::string& directory_resource_id,
238 const GetResourceListCallback& callback) {
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240 DCHECK(!directory_resource_id.empty());
241 DCHECK(!callback.is_null());
243 GetResourceListInternal(0, // start changestamp
244 std::string(), // empty search query
245 directory_resource_id,
246 0, // start offset
247 default_max_results_,
248 callback);
251 void FakeDriveService::Search(const std::string& search_query,
252 const GetResourceListCallback& callback) {
253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254 DCHECK(!search_query.empty());
255 DCHECK(!callback.is_null());
257 GetResourceListInternal(0, // start changestamp
258 search_query,
259 std::string(), // no directory resource id,
260 0, // start offset
261 default_max_results_,
262 callback);
265 void FakeDriveService::SearchInDirectory(
266 const std::string& search_query,
267 const std::string& directory_resource_id,
268 const GetResourceListCallback& callback) {
269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
270 DCHECK(!search_query.empty());
271 DCHECK(!directory_resource_id.empty());
272 DCHECK(!callback.is_null());
274 GetResourceListInternal(0, // start changestamp
275 search_query,
276 directory_resource_id,
277 0, // start offset
278 default_max_results_,
279 callback);
282 void FakeDriveService::GetChangeList(int64 start_changestamp,
283 const GetResourceListCallback& callback) {
284 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
285 DCHECK(!callback.is_null());
287 GetResourceListInternal(start_changestamp,
288 std::string(), // empty search query
289 std::string(), // no directory resource id,
290 0, // start offset
291 default_max_results_,
292 callback);
295 void FakeDriveService::ContinueGetResourceList(
296 const GURL& override_url,
297 const GetResourceListCallback& callback) {
298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
299 DCHECK(!override_url.is_empty());
300 DCHECK(!callback.is_null());
302 // "changestamp", "q", "parent" and "start-offset" are parameters to
303 // implement "paging" of the result on FakeDriveService.
304 // The URL should be the one filled in GetResourceListInternal of the
305 // previous method invocation, so it should start with "http://localhost/?".
306 // See also GetResourceListInternal.
307 DCHECK_EQ(override_url.host(), "localhost");
308 DCHECK_EQ(override_url.path(), "/");
310 int64 start_changestamp = 0;
311 std::string search_query;
312 std::string directory_resource_id;
313 int start_offset = 0;
314 int max_results = default_max_results_;
315 std::vector<std::pair<std::string, std::string> > parameters;
316 if (base::SplitStringIntoKeyValuePairs(
317 override_url.query(), '=', '&', &parameters)) {
318 for (size_t i = 0; i < parameters.size(); ++i) {
319 if (parameters[i].first == "changestamp") {
320 base::StringToInt64(parameters[i].second, &start_changestamp);
321 } else if (parameters[i].first == "q") {
322 search_query = parameters[i].second;
323 } else if (parameters[i].first == "parent") {
324 directory_resource_id = parameters[i].second;
325 } else if (parameters[i].first == "start-offset") {
326 base::StringToInt(parameters[i].second, &start_offset);
327 } else if (parameters[i].first == "max-results") {
328 base::StringToInt(parameters[i].second, &max_results);
333 GetResourceListInternal(
334 start_changestamp, search_query, directory_resource_id,
335 start_offset, max_results, callback);
338 void FakeDriveService::GetResourceEntry(
339 const std::string& resource_id,
340 const GetResourceEntryCallback& callback) {
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
342 DCHECK(!callback.is_null());
344 if (offline_) {
345 scoped_ptr<ResourceEntry> null;
346 MessageLoop::current()->PostTask(
347 FROM_HERE,
348 base::Bind(callback,
349 GDATA_NO_CONNECTION,
350 base::Passed(&null)));
351 return;
354 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
355 if (entry) {
356 scoped_ptr<ResourceEntry> resource_entry =
357 ResourceEntry::CreateFrom(*entry);
358 MessageLoop::current()->PostTask(
359 FROM_HERE,
360 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
361 return;
364 scoped_ptr<ResourceEntry> null;
365 MessageLoop::current()->PostTask(
366 FROM_HERE,
367 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
370 void FakeDriveService::GetAccountMetadata(
371 const GetAccountMetadataCallback& callback) {
372 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
373 DCHECK(!callback.is_null());
375 if (offline_) {
376 scoped_ptr<AccountMetadata> null;
377 MessageLoop::current()->PostTask(
378 FROM_HERE,
379 base::Bind(callback,
380 GDATA_NO_CONNECTION,
381 base::Passed(&null)));
382 return;
385 ++account_metadata_load_count_;
386 scoped_ptr<AccountMetadata> account_metadata =
387 AccountMetadata::CreateFrom(*account_metadata_value_);
388 // Overwrite the change stamp.
389 account_metadata->set_largest_changestamp(largest_changestamp_);
390 MessageLoop::current()->PostTask(
391 FROM_HERE,
392 base::Bind(callback,
393 HTTP_SUCCESS,
394 base::Passed(&account_metadata)));
397 void FakeDriveService::GetAboutResource(
398 const GetAboutResourceCallback& callback) {
399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
400 DCHECK(!callback.is_null());
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
403 DCHECK(!callback.is_null());
405 if (offline_) {
406 scoped_ptr<AboutResource> null;
407 MessageLoop::current()->PostTask(
408 FROM_HERE,
409 base::Bind(callback,
410 GDATA_NO_CONNECTION, base::Passed(&null)));
411 return;
414 ++about_resource_load_count_;
415 scoped_ptr<AboutResource> about_resource(
416 AboutResource::CreateFromAccountMetadata(
417 *AccountMetadata::CreateFrom(*account_metadata_value_),
418 GetRootResourceId()));
419 // Overwrite the change id.
420 about_resource->set_largest_change_id(largest_changestamp_);
421 MessageLoop::current()->PostTask(
422 FROM_HERE,
423 base::Bind(callback,
424 HTTP_SUCCESS, base::Passed(&about_resource)));
427 void FakeDriveService::GetAppList(const GetAppListCallback& callback) {
428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
429 DCHECK(!callback.is_null());
431 if (offline_) {
432 scoped_ptr<AppList> null;
433 MessageLoop::current()->PostTask(
434 FROM_HERE,
435 base::Bind(callback,
436 GDATA_NO_CONNECTION,
437 base::Passed(&null)));
438 return;
441 scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_));
442 MessageLoop::current()->PostTask(
443 FROM_HERE,
444 base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list)));
447 void FakeDriveService::DeleteResource(
448 const std::string& resource_id,
449 const std::string& etag,
450 const EntryActionCallback& callback) {
451 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
452 DCHECK(!callback.is_null());
454 if (offline_) {
455 MessageLoop::current()->PostTask(
456 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
457 return;
460 base::DictionaryValue* resource_list_dict = NULL;
461 base::ListValue* entries = NULL;
462 // Go through entries and remove the one that matches |resource_id|.
463 if (resource_list_value_->GetAsDictionary(&resource_list_dict) &&
464 resource_list_dict->GetList("entry", &entries)) {
465 for (size_t i = 0; i < entries->GetSize(); ++i) {
466 base::DictionaryValue* entry = NULL;
467 std::string current_resource_id;
468 if (entries->GetDictionary(i, &entry) &&
469 entry->GetString("gd$resourceId.$t", &current_resource_id) &&
470 resource_id == current_resource_id) {
471 entries->Remove(i, NULL);
472 MessageLoop::current()->PostTask(
473 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
474 return;
479 // TODO(satorux): Add support for returning "deleted" entries in
480 // changelists from GetResourceList().
481 MessageLoop::current()->PostTask(
482 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
485 void FakeDriveService::DownloadFile(
486 const base::FilePath& virtual_path,
487 const base::FilePath& local_cache_path,
488 const GURL& download_url,
489 const DownloadActionCallback& download_action_callback,
490 const GetContentCallback& get_content_callback,
491 const ProgressCallback& progress_callback) {
492 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
493 DCHECK(!download_action_callback.is_null());
495 if (offline_) {
496 MessageLoop::current()->PostTask(
497 FROM_HERE,
498 base::Bind(download_action_callback,
499 GDATA_NO_CONNECTION,
500 base::FilePath()));
501 return;
504 // The field content.src is the URL to download the file.
505 base::DictionaryValue* entry = FindEntryByContentUrl(download_url);
506 if (!entry) {
507 base::MessageLoopProxy::current()->PostTask(
508 FROM_HERE,
509 base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath()));
510 return;
513 // Write "x"s of the file size specified in the entry.
514 std::string file_size_string;
515 entry->GetString("docs$size.$t", &file_size_string);
516 // TODO(satorux): To be correct, we should update docs$md5Checksum.$t here.
517 int64 file_size = 0;
518 if (base::StringToInt64(file_size_string, &file_size)) {
519 std::string content(file_size, 'x');
520 DCHECK_EQ(static_cast<size_t>(file_size), content.size());
522 if (!get_content_callback.is_null()) {
523 const int64 kBlockSize = 5;
524 for (int64 i = 0; i < file_size; i += kBlockSize) {
525 const int64 size = std::min(kBlockSize, file_size - i);
526 scoped_ptr<std::string> content_for_callback(
527 new std::string(content.substr(i, size)));
528 base::MessageLoopProxy::current()->PostTask(
529 FROM_HERE,
530 base::Bind(get_content_callback, HTTP_SUCCESS,
531 base::Passed(&content_for_callback)));
535 if (static_cast<int>(content.size()) ==
536 file_util::WriteFile(local_cache_path,
537 content.data(),
538 content.size())) {
539 if (!progress_callback.is_null()) {
540 // See also the comment in ResumeUpload(). For testing that clients
541 // can handle the case progress_callback is called multiple times,
542 // here we invoke the callback twice.
543 base::MessageLoopProxy::current()->PostTask(
544 FROM_HERE,
545 base::Bind(progress_callback, file_size / 2, file_size));
546 base::MessageLoopProxy::current()->PostTask(
547 FROM_HERE,
548 base::Bind(progress_callback, file_size, file_size));
550 base::MessageLoopProxy::current()->PostTask(
551 FROM_HERE,
552 base::Bind(download_action_callback,
553 HTTP_SUCCESS,
554 local_cache_path));
555 return;
559 // Failed to write the content.
560 base::MessageLoopProxy::current()->PostTask(
561 FROM_HERE,
562 base::Bind(download_action_callback, GDATA_FILE_ERROR, base::FilePath()));
565 void FakeDriveService::CopyHostedDocument(
566 const std::string& resource_id,
567 const std::string& new_name,
568 const GetResourceEntryCallback& callback) {
569 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
570 DCHECK(!callback.is_null());
572 if (offline_) {
573 scoped_ptr<ResourceEntry> null;
574 MessageLoop::current()->PostTask(
575 FROM_HERE,
576 base::Bind(callback,
577 GDATA_NO_CONNECTION,
578 base::Passed(&null)));
579 return;
582 base::DictionaryValue* resource_list_dict = NULL;
583 base::ListValue* entries = NULL;
584 // Go through entries and copy the one that matches |resource_id|.
585 if (resource_list_value_->GetAsDictionary(&resource_list_dict) &&
586 resource_list_dict->GetList("entry", &entries)) {
587 for (size_t i = 0; i < entries->GetSize(); ++i) {
588 base::DictionaryValue* entry = NULL;
589 base::ListValue* categories = NULL;
590 std::string current_resource_id;
591 if (entries->GetDictionary(i, &entry) &&
592 entry->GetString("gd$resourceId.$t", &current_resource_id) &&
593 resource_id == current_resource_id &&
594 entry->GetList("category", &categories)) {
595 // Check that the resource is a hosted document. We consider it a
596 // hosted document if the kind is neither "folder" nor "file".
597 for (size_t k = 0; k < categories->GetSize(); ++k) {
598 base::DictionaryValue* category = NULL;
599 std::string scheme, term;
600 if (categories->GetDictionary(k, &category) &&
601 category->GetString("scheme", &scheme) &&
602 category->GetString("term", &term) &&
603 scheme == "http://schemas.google.com/g/2005#kind" &&
604 term != "http://schemas.google.com/docs/2007#file" &&
605 term != "http://schemas.google.com/docs/2007#folder") {
606 // Make a copy and set the new resource ID and the new title.
607 scoped_ptr<DictionaryValue> copied_entry(entry->DeepCopy());
608 copied_entry->SetString("gd$resourceId.$t",
609 resource_id + "_copied");
610 copied_entry->SetString("title.$t", new_name);
612 AddNewChangestamp(copied_entry.get());
614 // Parse the new entry.
615 scoped_ptr<ResourceEntry> resource_entry =
616 ResourceEntry::CreateFrom(*copied_entry);
617 // Add it to the resource list.
618 entries->Append(copied_entry.release());
620 MessageLoop::current()->PostTask(
621 FROM_HERE,
622 base::Bind(callback,
623 HTTP_SUCCESS,
624 base::Passed(&resource_entry)));
625 return;
632 scoped_ptr<ResourceEntry> null;
633 MessageLoop::current()->PostTask(
634 FROM_HERE,
635 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
638 void FakeDriveService::RenameResource(
639 const std::string& resource_id,
640 const std::string& new_name,
641 const EntryActionCallback& callback) {
642 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
643 DCHECK(!callback.is_null());
645 if (offline_) {
646 MessageLoop::current()->PostTask(
647 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
648 return;
651 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
652 if (entry) {
653 entry->SetString("title.$t", new_name);
654 AddNewChangestamp(entry);
655 MessageLoop::current()->PostTask(
656 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
657 return;
660 MessageLoop::current()->PostTask(
661 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
664 void FakeDriveService::AddResourceToDirectory(
665 const std::string& parent_resource_id,
666 const std::string& resource_id,
667 const EntryActionCallback& callback) {
668 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
669 DCHECK(!callback.is_null());
671 if (offline_) {
672 MessageLoop::current()->PostTask(
673 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
674 return;
677 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
678 if (entry) {
679 base::ListValue* links = NULL;
680 if (!entry->GetList("link", &links)) {
681 links = new base::ListValue;
682 entry->Set("link", links);
685 // On the real Drive server, resources do not necessary shape a tree
686 // structure. That is, each resource can have multiple parent.
687 // We mimic the behavior here; AddResourceToDirectoy just adds
688 // one more parent link, not overwriting old links.
689 base::DictionaryValue* link = new base::DictionaryValue;
690 link->SetString("rel", "http://schemas.google.com/docs/2007#parent");
691 link->SetString(
692 "href", GetFakeLinkUrl(parent_resource_id).spec());
693 links->Append(link);
695 AddNewChangestamp(entry);
696 MessageLoop::current()->PostTask(
697 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
698 return;
701 MessageLoop::current()->PostTask(
702 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
705 void FakeDriveService::RemoveResourceFromDirectory(
706 const std::string& parent_resource_id,
707 const std::string& resource_id,
708 const EntryActionCallback& callback) {
709 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
710 DCHECK(!callback.is_null());
712 if (offline_) {
713 MessageLoop::current()->PostTask(
714 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
715 return;
718 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
719 if (entry) {
720 base::ListValue* links = NULL;
721 if (entry->GetList("link", &links)) {
722 GURL parent_content_url = GetFakeLinkUrl(parent_resource_id);
723 for (size_t i = 0; i < links->GetSize(); ++i) {
724 base::DictionaryValue* link = NULL;
725 std::string rel;
726 std::string href;
727 if (links->GetDictionary(i, &link) &&
728 link->GetString("rel", &rel) &&
729 link->GetString("href", &href) &&
730 rel == "http://schemas.google.com/docs/2007#parent" &&
731 GURL(href) == parent_content_url) {
732 links->Remove(i, NULL);
733 AddNewChangestamp(entry);
734 MessageLoop::current()->PostTask(
735 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
736 return;
742 MessageLoop::current()->PostTask(
743 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
746 void FakeDriveService::AddNewDirectory(
747 const std::string& parent_resource_id,
748 const std::string& directory_name,
749 const GetResourceEntryCallback& callback) {
750 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
751 DCHECK(!callback.is_null());
753 if (offline_) {
754 scoped_ptr<ResourceEntry> null;
755 MessageLoop::current()->PostTask(
756 FROM_HERE,
757 base::Bind(callback,
758 GDATA_NO_CONNECTION,
759 base::Passed(&null)));
760 return;
763 const char kContentType[] = "application/atom+xml;type=feed";
764 const base::DictionaryValue* new_entry = AddNewEntry(kContentType,
765 0, // content_length
766 parent_resource_id,
767 directory_name,
768 "folder");
769 if (!new_entry) {
770 scoped_ptr<ResourceEntry> null;
771 MessageLoop::current()->PostTask(
772 FROM_HERE,
773 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
774 return;
777 scoped_ptr<ResourceEntry> parsed_entry(ResourceEntry::CreateFrom(*new_entry));
778 MessageLoop::current()->PostTask(
779 FROM_HERE,
780 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
783 void FakeDriveService::InitiateUploadNewFile(
784 const base::FilePath& drive_file_path,
785 const std::string& content_type,
786 int64 content_length,
787 const std::string& parent_resource_id,
788 const std::string& title,
789 const InitiateUploadCallback& callback) {
790 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
791 DCHECK(!callback.is_null());
793 if (offline_) {
794 MessageLoop::current()->PostTask(
795 FROM_HERE,
796 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
797 return;
800 // Content length should be zero, as we'll create an empty file first. The
801 // content will be added in ResumeUpload().
802 const base::DictionaryValue* new_entry = AddNewEntry(content_type,
803 0, // content_length
804 parent_resource_id,
805 title,
806 "file");
807 if (!new_entry) {
808 MessageLoop::current()->PostTask(
809 FROM_HERE,
810 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
811 return;
813 const GURL upload_url = GetUploadUrl(*new_entry);
814 DCHECK(upload_url.is_valid());
816 MessageLoop::current()->PostTask(
817 FROM_HERE,
818 base::Bind(callback, HTTP_SUCCESS, upload_url));
821 void FakeDriveService::InitiateUploadExistingFile(
822 const base::FilePath& drive_file_path,
823 const std::string& content_type,
824 int64 content_length,
825 const std::string& resource_id,
826 const std::string& etag,
827 const InitiateUploadCallback& callback) {
828 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
829 DCHECK(!callback.is_null());
831 if (offline_) {
832 MessageLoop::current()->PostTask(
833 FROM_HERE,
834 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
835 return;
838 DictionaryValue* entry = FindEntryByResourceId(resource_id);
839 if (!entry) {
840 MessageLoop::current()->PostTask(
841 FROM_HERE,
842 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
843 return;
846 std::string entry_etag;
847 entry->GetString("gd$etag", &entry_etag);
848 if (!etag.empty() && etag != entry_etag) {
849 MessageLoop::current()->PostTask(
850 FROM_HERE,
851 base::Bind(callback, HTTP_PRECONDITION, GURL()));
852 return;
854 entry->SetString("docs$size.$t", "0");
856 const GURL upload_url = GetUploadUrl(*entry);
857 DCHECK(upload_url.is_valid());
859 MessageLoop::current()->PostTask(
860 FROM_HERE,
861 base::Bind(callback, HTTP_SUCCESS, upload_url));
864 void FakeDriveService::GetUploadStatus(
865 UploadMode upload_mode,
866 const base::FilePath& drive_file_path,
867 const GURL& upload_url,
868 int64 content_length,
869 const UploadRangeCallback& callback) {
870 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
871 DCHECK(!callback.is_null());
874 void FakeDriveService::ResumeUpload(
875 UploadMode upload_mode,
876 const base::FilePath& drive_file_path,
877 const GURL& upload_url,
878 int64 start_position,
879 int64 end_position,
880 int64 content_length,
881 const std::string& content_type,
882 const scoped_refptr<net::IOBuffer>& buf,
883 const UploadRangeCallback& callback,
884 const ProgressCallback& progress_callback) {
885 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
886 DCHECK(!callback.is_null());
888 scoped_ptr<ResourceEntry> result_entry;
890 if (offline_) {
891 MessageLoop::current()->PostTask(
892 FROM_HERE,
893 base::Bind(callback,
894 UploadRangeResponse(GDATA_NO_CONNECTION,
895 start_position,
896 end_position),
897 base::Passed(&result_entry)));
898 return;
901 DictionaryValue* entry = NULL;
902 entry = FindEntryByUploadUrl(upload_url);
903 if (!entry) {
904 MessageLoop::current()->PostTask(
905 FROM_HERE,
906 base::Bind(callback,
907 UploadRangeResponse(HTTP_NOT_FOUND,
908 start_position,
909 end_position),
910 base::Passed(&result_entry)));
911 return;
914 // Chunks are required to be sent in such a ways that they fill from the start
915 // of the not-yet-uploaded part with no gaps nor overlaps.
916 std::string current_size_string;
917 int64 current_size;
918 if (!entry->GetString("docs$size.$t", &current_size_string) ||
919 !base::StringToInt64(current_size_string, &current_size) ||
920 current_size != start_position) {
921 MessageLoop::current()->PostTask(
922 FROM_HERE,
923 base::Bind(callback,
924 UploadRangeResponse(HTTP_BAD_REQUEST,
925 start_position,
926 end_position),
927 base::Passed(&result_entry)));
928 return;
931 entry->SetString("docs$size.$t", base::Int64ToString(end_position));
933 if (!progress_callback.is_null()) {
934 // In the real GDataWapi/Drive DriveService, progress is reported in
935 // nondeterministic timing. In this fake implementation, we choose to call
936 // it twice per one ResumeUpload. This is for making sure that client code
937 // works fine even if the callback is invoked more than once; it is the
938 // crucial difference of the progress callback from others.
939 // Note that progress is notified in the relative offset in each chunk.
940 const int64 chunk_size = end_position - start_position;
941 MessageLoop::current()->PostTask(
942 FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size));
943 MessageLoop::current()->PostTask(
944 FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size));
947 if (content_length != end_position) {
948 MessageLoop::current()->PostTask(
949 FROM_HERE,
950 base::Bind(callback,
951 UploadRangeResponse(HTTP_RESUME_INCOMPLETE,
952 start_position,
953 end_position),
954 base::Passed(&result_entry)));
955 return;
958 result_entry = ResourceEntry::CreateFrom(*entry).Pass();
960 GDataErrorCode return_code = HTTP_SUCCESS;
961 if (upload_mode == UPLOAD_NEW_FILE)
962 return_code = HTTP_CREATED;
964 MessageLoop::current()->PostTask(
965 FROM_HERE,
966 base::Bind(callback,
967 UploadRangeResponse(return_code,
968 start_position,
969 end_position),
970 base::Passed(&result_entry)));
973 void FakeDriveService::AuthorizeApp(const std::string& resource_id,
974 const std::string& app_id,
975 const AuthorizeAppCallback& callback) {
976 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
977 DCHECK(!callback.is_null());
980 void FakeDriveService::AddNewFile(const std::string& content_type,
981 int64 content_length,
982 const std::string& parent_resource_id,
983 const std::string& title,
984 const GetResourceEntryCallback& callback) {
985 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
986 DCHECK(!callback.is_null());
988 if (offline_) {
989 scoped_ptr<ResourceEntry> null;
990 MessageLoop::current()->PostTask(
991 FROM_HERE,
992 base::Bind(callback,
993 GDATA_NO_CONNECTION,
994 base::Passed(&null)));
995 return;
998 const base::DictionaryValue* new_entry = AddNewEntry(content_type,
999 content_length,
1000 parent_resource_id,
1001 title,
1002 "file");
1003 if (!new_entry) {
1004 scoped_ptr<ResourceEntry> null;
1005 MessageLoop::current()->PostTask(
1006 FROM_HERE,
1007 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1008 return;
1011 scoped_ptr<ResourceEntry> parsed_entry(
1012 ResourceEntry::CreateFrom(*new_entry));
1013 MessageLoop::current()->PostTask(
1014 FROM_HERE,
1015 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
1018 void FakeDriveService::SetLastModifiedTime(
1019 const std::string& resource_id,
1020 const base::Time& last_modified_time,
1021 const GetResourceEntryCallback& callback) {
1022 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1023 DCHECK(!callback.is_null());
1025 if (offline_) {
1026 scoped_ptr<ResourceEntry> null;
1027 MessageLoop::current()->PostTask(
1028 FROM_HERE,
1029 base::Bind(callback,
1030 GDATA_NO_CONNECTION,
1031 base::Passed(&null)));
1032 return;
1035 base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
1036 if (!entry) {
1037 scoped_ptr<ResourceEntry> null;
1038 MessageLoop::current()->PostTask(
1039 FROM_HERE,
1040 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null)));
1041 return;
1044 entry->SetString("updated.$t", util::FormatTimeAsString(last_modified_time));
1046 scoped_ptr<ResourceEntry> parsed_entry(
1047 ResourceEntry::CreateFrom(*entry));
1048 MessageLoop::current()->PostTask(
1049 FROM_HERE,
1050 base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry)));
1053 base::DictionaryValue* FakeDriveService::FindEntryByResourceId(
1054 const std::string& resource_id) {
1055 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1057 base::DictionaryValue* resource_list_dict = NULL;
1058 base::ListValue* entries = NULL;
1059 // Go through entries and return the one that matches |resource_id|.
1060 if (resource_list_value_->GetAsDictionary(&resource_list_dict) &&
1061 resource_list_dict->GetList("entry", &entries)) {
1062 for (size_t i = 0; i < entries->GetSize(); ++i) {
1063 base::DictionaryValue* entry = NULL;
1064 std::string current_resource_id;
1065 if (entries->GetDictionary(i, &entry) &&
1066 entry->GetString("gd$resourceId.$t", &current_resource_id) &&
1067 resource_id == current_resource_id) {
1068 return entry;
1073 return NULL;
1076 base::DictionaryValue* FakeDriveService::FindEntryByContentUrl(
1077 const GURL& content_url) {
1078 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1080 base::DictionaryValue* resource_list_dict = NULL;
1081 base::ListValue* entries = NULL;
1082 // Go through entries and return the one that matches |content_url|.
1083 if (resource_list_value_->GetAsDictionary(&resource_list_dict) &&
1084 resource_list_dict->GetList("entry", &entries)) {
1085 for (size_t i = 0; i < entries->GetSize(); ++i) {
1086 base::DictionaryValue* entry = NULL;
1087 std::string current_content_url;
1088 if (entries->GetDictionary(i, &entry) &&
1089 entry->GetString("content.src", &current_content_url) &&
1090 content_url == GURL(current_content_url)) {
1091 return entry;
1096 return NULL;
1099 base::DictionaryValue* FakeDriveService::FindEntryByUploadUrl(
1100 const GURL& upload_url) {
1101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1103 base::DictionaryValue* resource_list_dict = NULL;
1104 base::ListValue* entries = NULL;
1105 // Go through entries and return the one that matches |upload_url|.
1106 if (resource_list_value_->GetAsDictionary(&resource_list_dict) &&
1107 resource_list_dict->GetList("entry", &entries)) {
1108 for (size_t i = 0; i < entries->GetSize(); ++i) {
1109 base::DictionaryValue* entry = NULL;
1110 base::ListValue* links = NULL;
1111 if (entries->GetDictionary(i, &entry) &&
1112 entry->GetList("link", &links) &&
1113 links) {
1114 for (size_t link_index = 0;
1115 link_index < links->GetSize();
1116 ++link_index) {
1117 base::DictionaryValue* link = NULL;
1118 std::string rel;
1119 std::string found_upload_url;
1120 if (links->GetDictionary(link_index, &link) &&
1121 link && link->GetString("rel", &rel) &&
1122 rel == kUploadUrlRel &&
1123 link->GetString("href", &found_upload_url) &&
1124 GURL(found_upload_url) == upload_url) {
1125 return entry;
1132 return NULL;
1135 std::string FakeDriveService::GetNewResourceId() {
1136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1138 ++resource_id_count_;
1139 return base::StringPrintf("resource_id_%d", resource_id_count_);
1142 void FakeDriveService::AddNewChangestamp(base::DictionaryValue* entry) {
1143 ++largest_changestamp_;
1144 entry->SetString("docs$changestamp.value",
1145 base::Int64ToString(largest_changestamp_));
1148 const base::DictionaryValue* FakeDriveService::AddNewEntry(
1149 const std::string& content_type,
1150 int64 content_length,
1151 const std::string& parent_resource_id,
1152 const std::string& title,
1153 const std::string& entry_kind) {
1154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1156 if (parent_resource_id != GetRootResourceId() &&
1157 !FindEntryByResourceId(parent_resource_id)) {
1158 return NULL;
1161 base::DictionaryValue* resource_list_dict = NULL;
1162 if (!resource_list_value_->GetAsDictionary(&resource_list_dict))
1163 return NULL;
1165 std::string resource_id = GetNewResourceId();
1166 GURL upload_url = GURL("https://xxx/upload/" + resource_id);
1168 scoped_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue);
1169 // Set the resource ID and the title
1170 new_entry->SetString("gd$resourceId.$t", resource_id);
1171 new_entry->SetString("title.$t", title);
1172 new_entry->SetString("docs$filename", title);
1173 // Set the content size and MD5 for a file.
1174 if (entry_kind == "file") {
1175 new_entry->SetString("docs$size.$t", base::Int64ToString(content_length));
1176 // TODO(satorux): Set the correct MD5 here.
1177 new_entry->SetString("docs$md5Checksum.$t",
1178 "3b4385ebefec6e743574c76bbd0575de");
1181 // Add "category" which sets the resource type to |entry_kind|.
1182 base::ListValue* categories = new base::ListValue;
1183 base::DictionaryValue* category = new base::DictionaryValue;
1184 category->SetString("scheme", "http://schemas.google.com/g/2005#kind");
1185 category->SetString("term", "http://schemas.google.com/docs/2007#" +
1186 entry_kind);
1187 categories->Append(category);
1188 new_entry->Set("category", categories);
1190 // Add "content" which sets the content URL.
1191 base::DictionaryValue* content = new base::DictionaryValue;
1192 content->SetString("src", "https://xxx/content/" + resource_id);
1193 content->SetString("type", content_type);
1194 new_entry->Set("content", content);
1196 // Add "link" which sets the parent URL, the edit URL and the upload URL.
1197 base::ListValue* links = new base::ListValue;
1199 base::DictionaryValue* parent_link = new base::DictionaryValue;
1200 parent_link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec());
1201 parent_link->SetString("rel",
1202 "http://schemas.google.com/docs/2007#parent");
1203 links->Append(parent_link);
1205 base::DictionaryValue* edit_link = new base::DictionaryValue;
1206 edit_link->SetString("href", "https://xxx/edit/" + resource_id);
1207 edit_link->SetString("rel", "edit");
1208 links->Append(edit_link);
1210 base::DictionaryValue* upload_link = new base::DictionaryValue;
1211 upload_link->SetString("href", upload_url.spec());
1212 upload_link->SetString("rel", kUploadUrlRel);
1213 links->Append(upload_link);
1214 new_entry->Set("link", links);
1216 AddNewChangestamp(new_entry.get());
1218 // If there are no entries, prepare an empty entry to add.
1219 if (!resource_list_dict->HasKey("entry"))
1220 resource_list_dict->Set("entry", new ListValue);
1222 base::DictionaryValue* raw_new_entry = new_entry.release();
1223 base::ListValue* entries = NULL;
1224 if (resource_list_dict->GetList("entry", &entries))
1225 entries->Append(raw_new_entry);
1227 return raw_new_entry;
1230 void FakeDriveService::GetResourceListInternal(
1231 int64 start_changestamp,
1232 const std::string& search_query,
1233 const std::string& directory_resource_id,
1234 int start_offset,
1235 int max_results,
1236 const GetResourceListCallback& callback) {
1237 if (offline_) {
1238 scoped_ptr<ResourceList> null;
1239 MessageLoop::current()->PostTask(
1240 FROM_HERE,
1241 base::Bind(callback,
1242 GDATA_NO_CONNECTION,
1243 base::Passed(&null)));
1244 return;
1247 scoped_ptr<ResourceList> resource_list =
1248 ResourceList::CreateFrom(*resource_list_value_);
1250 // Filter out entries per parameters like |directory_resource_id| and
1251 // |search_query|.
1252 ScopedVector<ResourceEntry>* entries = resource_list->mutable_entries();
1254 int num_entries_matched = 0;
1255 for (size_t i = 0; i < entries->size();) {
1256 ResourceEntry* entry = (*entries)[i];
1257 bool should_exclude = false;
1259 // If |directory_resource_id| is set, exclude the entry if it's not in
1260 // the target directory.
1261 if (!directory_resource_id.empty()) {
1262 // Get the parent resource ID of the entry.
1263 std::string parent_resource_id;
1264 const google_apis::Link* parent_link =
1265 entry->GetLinkByType(Link::LINK_PARENT);
1266 if (parent_link) {
1267 parent_resource_id =
1268 net::UnescapeURLComponent(parent_link->href().ExtractFileName(),
1269 net::UnescapeRule::URL_SPECIAL_CHARS);
1271 if (directory_resource_id != parent_resource_id)
1272 should_exclude = true;
1275 // If |search_query| is set, exclude the entry if it does not contain the
1276 // search query in the title.
1277 if (!should_exclude && !search_query.empty() &&
1278 !EntryMatchWithQuery(*entry, search_query)) {
1279 should_exclude = true;
1282 // If |start_changestamp| is set, exclude the entry if the
1283 // changestamp is older than |largest_changestamp|.
1284 // See https://developers.google.com/google-apps/documents-list/
1285 // #retrieving_all_changes_since_a_given_changestamp
1286 if (start_changestamp > 0 && entry->changestamp() < start_changestamp)
1287 should_exclude = true;
1289 // The entry matched the criteria for inclusion.
1290 if (!should_exclude)
1291 ++num_entries_matched;
1293 // If |start_offset| is set, exclude the entry if the entry is before the
1294 // start index. <= instead of < as |num_entries_matched| was
1295 // already incremented.
1296 if (start_offset > 0 && num_entries_matched <= start_offset)
1297 should_exclude = true;
1299 if (should_exclude)
1300 entries->erase(entries->begin() + i);
1301 else
1302 ++i;
1305 // If |max_results| is set, trim the entries if the number exceeded the max
1306 // results.
1307 if (max_results > 0 && entries->size() > static_cast<size_t>(max_results)) {
1308 entries->erase(entries->begin() + max_results, entries->end());
1309 // Adds the next URL.
1310 // Here, we embed information which is needed for continueing the
1311 // GetResourceList operation in the next invocation into url query
1312 // parameters.
1313 GURL next_url(base::StringPrintf(
1314 "http://localhost/?start-offset=%d&max-results=%d",
1315 start_offset + max_results,
1316 max_results));
1317 if (start_changestamp > 0) {
1318 next_url = net::AppendOrReplaceQueryParameter(
1319 next_url, "changestamp",
1320 base::Int64ToString(start_changestamp).c_str());
1322 if (!search_query.empty()) {
1323 next_url = net::AppendOrReplaceQueryParameter(
1324 next_url, "q", search_query);
1326 if (!directory_resource_id.empty()) {
1327 next_url = net::AppendOrReplaceQueryParameter(
1328 next_url, "parent", directory_resource_id);
1331 Link* link = new Link;
1332 link->set_type(Link::LINK_NEXT);
1333 link->set_href(next_url);
1334 resource_list->mutable_links()->push_back(link);
1337 ++resource_list_load_count_;
1338 MessageLoop::current()->PostTask(
1339 FROM_HERE,
1340 base::Bind(callback,
1341 HTTP_SUCCESS,
1342 base::Passed(&resource_list)));
1345 } // namespace google_apis