fake_drive_service.cc revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
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. 4 5#include "chrome/browser/drive/fake_drive_service.h" 6 7#include <string> 8 9#include "base/file_util.h" 10#include "base/logging.h" 11#include "base/md5.h" 12#include "base/message_loop/message_loop.h" 13#include "base/strings/string_number_conversions.h" 14#include "base/strings/string_split.h" 15#include "base/strings/string_tokenizer.h" 16#include "base/strings/string_util.h" 17#include "base/strings/stringprintf.h" 18#include "base/strings/utf_string_conversions.h" 19#include "chrome/browser/google_apis/drive_api_parser.h" 20#include "chrome/browser/google_apis/gdata_wapi_parser.h" 21#include "chrome/browser/google_apis/test_util.h" 22#include "chrome/browser/google_apis/time_util.h" 23#include "content/public/browser/browser_thread.h" 24#include "net/base/escape.h" 25#include "net/base/url_util.h" 26 27using content::BrowserThread; 28using google_apis::AboutResource; 29using google_apis::AboutResourceCallback; 30using google_apis::AccountMetadata; 31using google_apis::AppList; 32using google_apis::AppListCallback; 33using google_apis::AuthStatusCallback; 34using google_apis::AuthorizeAppCallback; 35using google_apis::CancelCallback; 36using google_apis::DownloadActionCallback; 37using google_apis::EntryActionCallback; 38using google_apis::GDATA_FILE_ERROR; 39using google_apis::GDATA_NO_CONNECTION; 40using google_apis::GDATA_OTHER_ERROR; 41using google_apis::GDataErrorCode; 42using google_apis::GetContentCallback; 43using google_apis::GetResourceEntryCallback; 44using google_apis::GetResourceListCallback; 45using google_apis::GetShareUrlCallback; 46using google_apis::HTTP_BAD_REQUEST; 47using google_apis::HTTP_CREATED; 48using google_apis::HTTP_NOT_FOUND; 49using google_apis::HTTP_PRECONDITION; 50using google_apis::HTTP_RESUME_INCOMPLETE; 51using google_apis::HTTP_SUCCESS; 52using google_apis::InitiateUploadCallback; 53using google_apis::Link; 54using google_apis::ProgressCallback; 55using google_apis::ResourceEntry; 56using google_apis::ResourceList; 57using google_apis::UploadRangeCallback; 58using google_apis::UploadRangeResponse; 59namespace test_util = google_apis::test_util; 60namespace util = google_apis::util; 61 62namespace drive { 63namespace { 64 65// Rel property of an upload link in the entries dictionary value. 66const char kUploadUrlRel[] = 67 "http://schemas.google.com/g/2005#resumable-create-media"; 68 69// Rel property of a share link in the entries dictionary value. 70const char kShareUrlRel[] = 71 "http://schemas.google.com/docs/2007#share"; 72 73// Returns true if a resource entry matches with the search query. 74// Supports queries consist of following format. 75// - Phrases quoted by double/single quotes 76// - AND search for multiple words/phrases segmented by space 77// - Limited attribute search. Only "title:" is supported. 78bool EntryMatchWithQuery(const ResourceEntry& entry, 79 const std::string& query) { 80 base::StringTokenizer tokenizer(query, " "); 81 tokenizer.set_quote_chars("\"'"); 82 while (tokenizer.GetNext()) { 83 std::string key, value; 84 const std::string& token = tokenizer.token(); 85 if (token.find(':') == std::string::npos) { 86 TrimString(token, "\"'", &value); 87 } else { 88 base::StringTokenizer key_value(token, ":"); 89 key_value.set_quote_chars("\"'"); 90 if (!key_value.GetNext()) 91 return false; 92 key = key_value.token(); 93 if (!key_value.GetNext()) 94 return false; 95 TrimString(key_value.token(), "\"'", &value); 96 } 97 98 // TODO(peria): Deal with other attributes than title. 99 if (!key.empty() && key != "title") 100 return false; 101 // Search query in the title. 102 if (entry.title().find(value) == std::string::npos) 103 return false; 104 } 105 return true; 106} 107 108// Returns |url| without query parameter. 109GURL RemoveQueryParameter(const GURL& url) { 110 GURL::Replacements replacements; 111 replacements.ClearQuery(); 112 return url.ReplaceComponents(replacements); 113} 114 115void ScheduleUploadRangeCallback(const UploadRangeCallback& callback, 116 int64 start_position, 117 int64 end_position, 118 GDataErrorCode error, 119 scoped_ptr<ResourceEntry> entry) { 120 base::MessageLoop::current()->PostTask( 121 FROM_HERE, 122 base::Bind(callback, 123 UploadRangeResponse(error, 124 start_position, 125 end_position), 126 base::Passed(&entry))); 127} 128 129void EntryActionCallbackAdapter( 130 const EntryActionCallback& callback, 131 GDataErrorCode error, scoped_ptr<ResourceEntry> resource_entry) { 132 callback.Run(error); 133} 134 135} // namespace 136 137struct FakeDriveService::UploadSession { 138 std::string content_type; 139 int64 content_length; 140 std::string parent_resource_id; 141 std::string resource_id; 142 std::string etag; 143 std::string title; 144 145 int64 uploaded_size; 146 147 UploadSession() 148 : content_length(0), 149 uploaded_size(0) {} 150 151 UploadSession( 152 std::string content_type, 153 int64 content_length, 154 std::string parent_resource_id, 155 std::string resource_id, 156 std::string etag, 157 std::string title) 158 : content_type(content_type), 159 content_length(content_length), 160 parent_resource_id(parent_resource_id), 161 resource_id(resource_id), 162 etag(etag), 163 title(title), 164 uploaded_size(0) { 165 } 166}; 167 168FakeDriveService::FakeDriveService() 169 : largest_changestamp_(0), 170 published_date_seq_(0), 171 next_upload_sequence_number_(0), 172 default_max_results_(0), 173 resource_id_count_(0), 174 resource_list_load_count_(0), 175 change_list_load_count_(0), 176 directory_load_count_(0), 177 about_resource_load_count_(0), 178 app_list_load_count_(0), 179 offline_(false) { 180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 181} 182 183FakeDriveService::~FakeDriveService() { 184 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 185} 186 187bool FakeDriveService::LoadResourceListForWapi( 188 const std::string& relative_path) { 189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 190 scoped_ptr<Value> raw_value = test_util::LoadJSONFile(relative_path); 191 base::DictionaryValue* as_dict = NULL; 192 scoped_ptr<base::Value> feed; 193 base::DictionaryValue* feed_as_dict = NULL; 194 195 // Extract the "feed" from the raw value and take the ownership. 196 // Note that Remove() transfers the ownership to |feed|. 197 if (raw_value->GetAsDictionary(&as_dict) && 198 as_dict->Remove("feed", &feed) && 199 feed->GetAsDictionary(&feed_as_dict)) { 200 ignore_result(feed.release()); 201 resource_list_value_.reset(feed_as_dict); 202 203 // Go through entries and convert test$data from a string to a binary blob. 204 base::ListValue* entries = NULL; 205 if (feed_as_dict->GetList("entry", &entries)) { 206 for (size_t i = 0; i < entries->GetSize(); ++i) { 207 base::DictionaryValue* entry = NULL; 208 std::string content_data; 209 if (entries->GetDictionary(i, &entry) && 210 entry->GetString("test$data", &content_data)) { 211 entry->Set("test$data", 212 base::BinaryValue::CreateWithCopiedBuffer( 213 content_data.c_str(), content_data.size())); 214 } 215 } 216 } 217 } 218 219 return resource_list_value_; 220} 221 222bool FakeDriveService::LoadAccountMetadataForWapi( 223 const std::string& relative_path) { 224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 225 226 // Load JSON data, which must be a dictionary. 227 scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path); 228 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType()); 229 account_metadata_value_.reset( 230 static_cast<base::DictionaryValue*>(value.release())); 231 232 // Update the largest_changestamp_. 233 scoped_ptr<AccountMetadata> account_metadata = 234 AccountMetadata::CreateFrom(*account_metadata_value_); 235 largest_changestamp_ = account_metadata->largest_changestamp(); 236 237 // Add the largest changestamp to the existing entries. 238 // This will be used to generate change lists in GetResourceList(). 239 if (resource_list_value_) { 240 base::ListValue* entries = NULL; 241 if (resource_list_value_->GetList("entry", &entries)) { 242 for (size_t i = 0; i < entries->GetSize(); ++i) { 243 base::DictionaryValue* entry = NULL; 244 if (entries->GetDictionary(i, &entry)) { 245 entry->SetString("docs$changestamp.value", 246 base::Int64ToString(largest_changestamp_)); 247 } 248 } 249 } 250 } 251 252 return account_metadata_value_; 253} 254 255bool FakeDriveService::LoadAppListForDriveApi( 256 const std::string& relative_path) { 257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 258 app_info_value_ = test_util::LoadJSONFile(relative_path); 259 return app_info_value_; 260} 261 262void FakeDriveService::SetQuotaValue(int64 used, int64 total) { 263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 264 DCHECK(account_metadata_value_); 265 266 account_metadata_value_->SetString("entry.gd$quotaBytesUsed.$t", 267 base::Int64ToString16(used)); 268 account_metadata_value_->SetString("entry.gd$quotaBytesTotal.$t", 269 base::Int64ToString16(total)); 270} 271 272GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) { 273 return GURL("https://fake_server/" + net::EscapePath(resource_id)); 274} 275 276void FakeDriveService::Initialize() { 277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 278} 279 280void FakeDriveService::AddObserver(DriveServiceObserver* observer) { 281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 282} 283 284void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) { 285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 286} 287 288bool FakeDriveService::CanSendRequest() const { 289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 290 return true; 291} 292 293std::string FakeDriveService::CanonicalizeResourceId( 294 const std::string& resource_id) const { 295 return resource_id; 296} 297 298bool FakeDriveService::HasAccessToken() const { 299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 300 return true; 301} 302 303void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) { 304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 305 DCHECK(!callback.is_null()); 306 callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token"); 307} 308 309bool FakeDriveService::HasRefreshToken() const { 310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 311 return true; 312} 313 314void FakeDriveService::ClearAccessToken() { 315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 316} 317 318void FakeDriveService::ClearRefreshToken() { 319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 320} 321 322std::string FakeDriveService::GetRootResourceId() const { 323 return "fake_root"; 324} 325 326CancelCallback FakeDriveService::GetAllResourceList( 327 const GetResourceListCallback& callback) { 328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 329 DCHECK(!callback.is_null()); 330 331 GetResourceListInternal(0, // start changestamp 332 std::string(), // empty search query 333 std::string(), // no directory resource id, 334 0, // start offset 335 default_max_results_, 336 &resource_list_load_count_, 337 callback); 338 return CancelCallback(); 339} 340 341CancelCallback FakeDriveService::GetResourceListInDirectory( 342 const std::string& directory_resource_id, 343 const GetResourceListCallback& callback) { 344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 345 DCHECK(!directory_resource_id.empty()); 346 DCHECK(!callback.is_null()); 347 348 GetResourceListInternal(0, // start changestamp 349 std::string(), // empty search query 350 directory_resource_id, 351 0, // start offset 352 default_max_results_, 353 &directory_load_count_, 354 callback); 355 return CancelCallback(); 356} 357 358CancelCallback FakeDriveService::Search( 359 const std::string& search_query, 360 const GetResourceListCallback& callback) { 361 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 362 DCHECK(!search_query.empty()); 363 DCHECK(!callback.is_null()); 364 365 GetResourceListInternal(0, // start changestamp 366 search_query, 367 std::string(), // no directory resource id, 368 0, // start offset 369 default_max_results_, 370 NULL, 371 callback); 372 return CancelCallback(); 373} 374 375CancelCallback FakeDriveService::SearchByTitle( 376 const std::string& title, 377 const std::string& directory_resource_id, 378 const GetResourceListCallback& callback) { 379 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 380 DCHECK(!title.empty()); 381 DCHECK(!callback.is_null()); 382 383 // Note: the search implementation here doesn't support quotation unescape, 384 // so don't escape here. 385 GetResourceListInternal(0, // start changestamp 386 base::StringPrintf("title:'%s'", title.c_str()), 387 directory_resource_id, 388 0, // start offset 389 default_max_results_, 390 NULL, 391 callback); 392 return CancelCallback(); 393} 394 395CancelCallback FakeDriveService::GetChangeList( 396 int64 start_changestamp, 397 const GetResourceListCallback& callback) { 398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 399 DCHECK(!callback.is_null()); 400 401 GetResourceListInternal(start_changestamp, 402 std::string(), // empty search query 403 std::string(), // no directory resource id, 404 0, // start offset 405 default_max_results_, 406 &change_list_load_count_, 407 callback); 408 return CancelCallback(); 409} 410 411CancelCallback FakeDriveService::ContinueGetResourceList( 412 const GURL& override_url, 413 const GetResourceListCallback& callback) { 414 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 415 DCHECK(!override_url.is_empty()); 416 DCHECK(!callback.is_null()); 417 418 // "changestamp", "q", "parent" and "start-offset" are parameters to 419 // implement "paging" of the result on FakeDriveService. 420 // The URL should be the one filled in GetResourceListInternal of the 421 // previous method invocation, so it should start with "http://localhost/?". 422 // See also GetResourceListInternal. 423 DCHECK_EQ(override_url.host(), "localhost"); 424 DCHECK_EQ(override_url.path(), "/"); 425 426 int64 start_changestamp = 0; 427 std::string search_query; 428 std::string directory_resource_id; 429 int start_offset = 0; 430 int max_results = default_max_results_; 431 std::vector<std::pair<std::string, std::string> > parameters; 432 if (base::SplitStringIntoKeyValuePairs( 433 override_url.query(), '=', '&', ¶meters)) { 434 for (size_t i = 0; i < parameters.size(); ++i) { 435 if (parameters[i].first == "changestamp") { 436 base::StringToInt64(parameters[i].second, &start_changestamp); 437 } else if (parameters[i].first == "q") { 438 search_query = 439 net::UnescapeURLComponent(parameters[i].second, 440 net::UnescapeRule::URL_SPECIAL_CHARS); 441 } else if (parameters[i].first == "parent") { 442 directory_resource_id = 443 net::UnescapeURLComponent(parameters[i].second, 444 net::UnescapeRule::URL_SPECIAL_CHARS); 445 } else if (parameters[i].first == "start-offset") { 446 base::StringToInt(parameters[i].second, &start_offset); 447 } else if (parameters[i].first == "max-results") { 448 base::StringToInt(parameters[i].second, &max_results); 449 } 450 } 451 } 452 453 GetResourceListInternal( 454 start_changestamp, search_query, directory_resource_id, 455 start_offset, max_results, NULL, callback); 456 return CancelCallback(); 457} 458 459CancelCallback FakeDriveService::GetRemainingChangeList( 460 const std::string& page_token, 461 const GetResourceListCallback& callback) { 462 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 463 DCHECK(!page_token.empty()); 464 DCHECK(!callback.is_null()); 465 466 return ContinueGetResourceList(GURL(page_token), callback); 467} 468 469CancelCallback FakeDriveService::GetRemainingFileList( 470 const std::string& page_token, 471 const GetResourceListCallback& callback) { 472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 473 DCHECK(!page_token.empty()); 474 DCHECK(!callback.is_null()); 475 476 return ContinueGetResourceList(GURL(page_token), callback); 477} 478 479CancelCallback FakeDriveService::GetResourceEntry( 480 const std::string& resource_id, 481 const GetResourceEntryCallback& callback) { 482 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 483 DCHECK(!callback.is_null()); 484 485 if (offline_) { 486 scoped_ptr<ResourceEntry> null; 487 base::MessageLoop::current()->PostTask( 488 FROM_HERE, 489 base::Bind(callback, 490 GDATA_NO_CONNECTION, 491 base::Passed(&null))); 492 return CancelCallback(); 493 } 494 495 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 496 if (entry) { 497 scoped_ptr<ResourceEntry> resource_entry = 498 ResourceEntry::CreateFrom(*entry); 499 base::MessageLoop::current()->PostTask( 500 FROM_HERE, 501 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry))); 502 return CancelCallback(); 503 } 504 505 scoped_ptr<ResourceEntry> null; 506 base::MessageLoop::current()->PostTask( 507 FROM_HERE, 508 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null))); 509 return CancelCallback(); 510} 511 512CancelCallback FakeDriveService::GetShareUrl( 513 const std::string& resource_id, 514 const GURL& /* embed_origin */, 515 const GetShareUrlCallback& callback) { 516 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 517 DCHECK(!callback.is_null()); 518 519 if (offline_) { 520 scoped_ptr<ResourceEntry> null; 521 base::MessageLoop::current()->PostTask( 522 FROM_HERE, 523 base::Bind(callback, 524 GDATA_NO_CONNECTION, 525 GURL())); 526 return CancelCallback(); 527 } 528 529 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 530 if (entry) { 531 // Share urls are stored in the resource entry, and they do not rely on the 532 // embedding origin. 533 scoped_ptr<ResourceEntry> resource_entry = 534 ResourceEntry::CreateFrom(*entry); 535 const Link* share_url = resource_entry->GetLinkByType(Link::LINK_SHARE); 536 if (share_url) { 537 base::MessageLoop::current()->PostTask( 538 FROM_HERE, 539 base::Bind(callback, HTTP_SUCCESS, share_url->href())); 540 } else { 541 base::MessageLoop::current()->PostTask( 542 FROM_HERE, 543 base::Bind(callback, HTTP_SUCCESS, GURL())); 544 } 545 return CancelCallback(); 546 } 547 548 scoped_ptr<ResourceEntry> null; 549 base::MessageLoop::current()->PostTask( 550 FROM_HERE, 551 base::Bind(callback, HTTP_NOT_FOUND, GURL())); 552 return CancelCallback(); 553} 554 555CancelCallback FakeDriveService::GetAboutResource( 556 const AboutResourceCallback& callback) { 557 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 558 DCHECK(!callback.is_null()); 559 560 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 561 DCHECK(!callback.is_null()); 562 563 if (offline_) { 564 scoped_ptr<AboutResource> null; 565 base::MessageLoop::current()->PostTask( 566 FROM_HERE, 567 base::Bind(callback, 568 GDATA_NO_CONNECTION, base::Passed(&null))); 569 return CancelCallback(); 570 } 571 572 ++about_resource_load_count_; 573 scoped_ptr<AboutResource> about_resource( 574 AboutResource::CreateFromAccountMetadata( 575 *AccountMetadata::CreateFrom(*account_metadata_value_), 576 GetRootResourceId())); 577 // Overwrite the change id. 578 about_resource->set_largest_change_id(largest_changestamp_); 579 base::MessageLoop::current()->PostTask( 580 FROM_HERE, 581 base::Bind(callback, 582 HTTP_SUCCESS, base::Passed(&about_resource))); 583 return CancelCallback(); 584} 585 586CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) { 587 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 588 DCHECK(!callback.is_null()); 589 DCHECK(app_info_value_); 590 591 if (offline_) { 592 scoped_ptr<AppList> null; 593 base::MessageLoop::current()->PostTask( 594 FROM_HERE, 595 base::Bind(callback, 596 GDATA_NO_CONNECTION, 597 base::Passed(&null))); 598 return CancelCallback(); 599 } 600 601 ++app_list_load_count_; 602 scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_)); 603 base::MessageLoop::current()->PostTask( 604 FROM_HERE, 605 base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list))); 606 return CancelCallback(); 607} 608 609CancelCallback FakeDriveService::DeleteResource( 610 const std::string& resource_id, 611 const std::string& etag, 612 const EntryActionCallback& callback) { 613 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 614 DCHECK(!callback.is_null()); 615 616 if (offline_) { 617 base::MessageLoop::current()->PostTask( 618 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION)); 619 return CancelCallback(); 620 } 621 622 base::ListValue* entries = NULL; 623 // Go through entries and remove the one that matches |resource_id|. 624 if (resource_list_value_->GetList("entry", &entries)) { 625 for (size_t i = 0; i < entries->GetSize(); ++i) { 626 base::DictionaryValue* entry = NULL; 627 std::string current_resource_id; 628 if (entries->GetDictionary(i, &entry) && 629 entry->GetString("gd$resourceId.$t", ¤t_resource_id) && 630 resource_id == current_resource_id) { 631 GDataErrorCode error = google_apis::GDATA_OTHER_ERROR; 632 if (entry->HasKey("gd$deleted")) { 633 error = HTTP_NOT_FOUND; 634 } else { 635 entry->Set("gd$deleted", new DictionaryValue); 636 AddNewChangestampAndETag(entry); 637 error = HTTP_SUCCESS; 638 } 639 base::MessageLoop::current()->PostTask( 640 FROM_HERE, base::Bind(callback, error)); 641 return CancelCallback(); 642 } 643 } 644 } 645 646 base::MessageLoop::current()->PostTask( 647 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); 648 return CancelCallback(); 649} 650 651CancelCallback FakeDriveService::DownloadFile( 652 const base::FilePath& local_cache_path, 653 const std::string& resource_id, 654 const DownloadActionCallback& download_action_callback, 655 const GetContentCallback& get_content_callback, 656 const ProgressCallback& progress_callback) { 657 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 658 DCHECK(!download_action_callback.is_null()); 659 660 if (offline_) { 661 base::MessageLoop::current()->PostTask( 662 FROM_HERE, 663 base::Bind(download_action_callback, 664 GDATA_NO_CONNECTION, 665 base::FilePath())); 666 return CancelCallback(); 667 } 668 669 // The field content.src is the URL to download the file. 670 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 671 if (!entry) { 672 base::MessageLoopProxy::current()->PostTask( 673 FROM_HERE, 674 base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath())); 675 return CancelCallback(); 676 } 677 678 // Write "x"s of the file size specified in the entry. 679 std::string file_size_string; 680 entry->GetString("docs$size.$t", &file_size_string); 681 int64 file_size = 0; 682 if (base::StringToInt64(file_size_string, &file_size)) { 683 base::BinaryValue* content_binary_data; 684 std::string content_data; 685 if (entry->GetBinary("test$data", &content_binary_data)) { 686 content_data = std::string(content_binary_data->GetBuffer(), 687 content_binary_data->GetSize()); 688 } 689 DCHECK_EQ(static_cast<size_t>(file_size), content_data.size()); 690 691 if (!get_content_callback.is_null()) { 692 const int64 kBlockSize = 5; 693 for (int64 i = 0; i < file_size; i += kBlockSize) { 694 const int64 size = std::min(kBlockSize, file_size - i); 695 scoped_ptr<std::string> content_for_callback( 696 new std::string(content_data.substr(i, size))); 697 base::MessageLoopProxy::current()->PostTask( 698 FROM_HERE, 699 base::Bind(get_content_callback, HTTP_SUCCESS, 700 base::Passed(&content_for_callback))); 701 } 702 } 703 704 if (test_util::WriteStringToFile(local_cache_path, content_data)) { 705 if (!progress_callback.is_null()) { 706 // See also the comment in ResumeUpload(). For testing that clients 707 // can handle the case progress_callback is called multiple times, 708 // here we invoke the callback twice. 709 base::MessageLoopProxy::current()->PostTask( 710 FROM_HERE, 711 base::Bind(progress_callback, file_size / 2, file_size)); 712 base::MessageLoopProxy::current()->PostTask( 713 FROM_HERE, 714 base::Bind(progress_callback, file_size, file_size)); 715 } 716 base::MessageLoopProxy::current()->PostTask( 717 FROM_HERE, 718 base::Bind(download_action_callback, 719 HTTP_SUCCESS, 720 local_cache_path)); 721 return CancelCallback(); 722 } 723 } 724 725 // Failed to write the content. 726 base::MessageLoopProxy::current()->PostTask( 727 FROM_HERE, 728 base::Bind(download_action_callback, GDATA_FILE_ERROR, base::FilePath())); 729 return CancelCallback(); 730} 731 732CancelCallback FakeDriveService::CopyResource( 733 const std::string& resource_id, 734 const std::string& in_parent_resource_id, 735 const std::string& new_title, 736 const GetResourceEntryCallback& callback) { 737 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 738 DCHECK(!callback.is_null()); 739 740 if (offline_) { 741 scoped_ptr<ResourceEntry> null; 742 base::MessageLoop::current()->PostTask( 743 FROM_HERE, 744 base::Bind(callback, 745 GDATA_NO_CONNECTION, 746 base::Passed(&null))); 747 return CancelCallback(); 748 } 749 750 const std::string& parent_resource_id = in_parent_resource_id.empty() ? 751 GetRootResourceId() : in_parent_resource_id; 752 753 base::ListValue* entries = NULL; 754 // Go through entries and copy the one that matches |resource_id|. 755 if (resource_list_value_->GetList("entry", &entries)) { 756 for (size_t i = 0; i < entries->GetSize(); ++i) { 757 base::DictionaryValue* entry = NULL; 758 std::string current_resource_id; 759 if (entries->GetDictionary(i, &entry) && 760 entry->GetString("gd$resourceId.$t", ¤t_resource_id) && 761 resource_id == current_resource_id) { 762 // Make a copy and set the new resource ID and the new title. 763 scoped_ptr<DictionaryValue> copied_entry(entry->DeepCopy()); 764 copied_entry->SetString("gd$resourceId.$t", 765 resource_id + "_copied"); 766 copied_entry->SetString("title.$t", new_title); 767 768 // Reset parent directory. 769 base::ListValue* links = NULL; 770 if (!copied_entry->GetList("link", &links)) { 771 links = new base::ListValue; 772 copied_entry->Set("link", links); 773 } 774 links->Clear(); 775 776 base::DictionaryValue* link = new base::DictionaryValue; 777 link->SetString( 778 "rel", "http://schemas.google.com/docs/2007#parent"); 779 link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec()); 780 links->Append(link); 781 782 AddNewChangestampAndETag(copied_entry.get()); 783 784 // Parse the new entry. 785 scoped_ptr<ResourceEntry> resource_entry = 786 ResourceEntry::CreateFrom(*copied_entry); 787 // Add it to the resource list. 788 entries->Append(copied_entry.release()); 789 790 base::MessageLoop::current()->PostTask( 791 FROM_HERE, 792 base::Bind(callback, 793 HTTP_SUCCESS, 794 base::Passed(&resource_entry))); 795 return CancelCallback(); 796 } 797 } 798 } 799 800 scoped_ptr<ResourceEntry> null; 801 base::MessageLoop::current()->PostTask( 802 FROM_HERE, 803 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null))); 804 return CancelCallback(); 805} 806 807CancelCallback FakeDriveService::CopyHostedDocument( 808 const std::string& resource_id, 809 const std::string& new_title, 810 const GetResourceEntryCallback& callback) { 811 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 812 DCHECK(!callback.is_null()); 813 814 return CopyResource(resource_id, std::string(), new_title, callback); 815} 816 817CancelCallback FakeDriveService::MoveResource( 818 const std::string& resource_id, 819 const std::string& parent_resource_id, 820 const std::string& new_title, 821 const google_apis::GetResourceEntryCallback& callback) { 822 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 823 DCHECK(!callback.is_null()); 824 825 if (offline_) { 826 base::MessageLoop::current()->PostTask( 827 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION, 828 base::Passed(scoped_ptr<ResourceEntry>()))); 829 return CancelCallback(); 830 } 831 832 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 833 if (entry) { 834 entry->SetString("title.$t", new_title); 835 836 // Set parent if necessary. 837 if (!parent_resource_id.empty()) { 838 base::ListValue* links = NULL; 839 if (!entry->GetList("link", &links)) { 840 links = new base::ListValue; 841 entry->Set("link", links); 842 } 843 844 // Remove old parent(s). 845 for (size_t i = 0; i < links->GetSize(); ) { 846 base::DictionaryValue* link = NULL; 847 std::string rel; 848 std::string href; 849 if (links->GetDictionary(i, &link) && 850 link->GetString("rel", &rel) && 851 link->GetString("href", &href) && 852 rel == "http://schemas.google.com/docs/2007#parent") { 853 links->Remove(i, NULL); 854 } else { 855 ++i; 856 } 857 } 858 859 base::DictionaryValue* link = new base::DictionaryValue; 860 link->SetString("rel", "http://schemas.google.com/docs/2007#parent"); 861 link->SetString( 862 "href", GetFakeLinkUrl(parent_resource_id).spec()); 863 links->Append(link); 864 } 865 866 AddNewChangestampAndETag(entry); 867 868 // Parse the new entry. 869 scoped_ptr<ResourceEntry> resource_entry = 870 ResourceEntry::CreateFrom(*entry); 871 base::MessageLoop::current()->PostTask( 872 FROM_HERE, 873 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry))); 874 return CancelCallback(); 875 } 876 877 base::MessageLoop::current()->PostTask( 878 FROM_HERE, 879 base::Bind(callback, HTTP_NOT_FOUND, 880 base::Passed(scoped_ptr<ResourceEntry>()))); 881 return CancelCallback(); 882} 883 884CancelCallback FakeDriveService::RenameResource( 885 const std::string& resource_id, 886 const std::string& new_title, 887 const EntryActionCallback& callback) { 888 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 889 DCHECK(!callback.is_null()); 890 891 return MoveResource( 892 resource_id, std::string(), new_title, 893 base::Bind(&EntryActionCallbackAdapter, callback)); 894} 895 896CancelCallback FakeDriveService::TouchResource( 897 const std::string& resource_id, 898 const base::Time& modified_date, 899 const base::Time& last_viewed_by_me_date, 900 const GetResourceEntryCallback& callback) { 901 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 902 DCHECK(!modified_date.is_null()); 903 DCHECK(!last_viewed_by_me_date.is_null()); 904 DCHECK(!callback.is_null()); 905 906 if (offline_) { 907 base::MessageLoop::current()->PostTask( 908 FROM_HERE, 909 base::Bind(callback, GDATA_NO_CONNECTION, 910 base::Passed(scoped_ptr<ResourceEntry>()))); 911 return CancelCallback(); 912 } 913 914 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 915 if (!entry) { 916 base::MessageLoop::current()->PostTask( 917 FROM_HERE, 918 base::Bind(callback, HTTP_NOT_FOUND, 919 base::Passed(scoped_ptr<ResourceEntry>()))); 920 return CancelCallback(); 921 } 922 923 entry->SetString("updated.$t", 924 util::FormatTimeAsString(modified_date)); 925 entry->SetString("gd$lastViewed.$t", 926 util::FormatTimeAsString(last_viewed_by_me_date)); 927 AddNewChangestampAndETag(entry); 928 929 scoped_ptr<ResourceEntry> parsed_entry(ResourceEntry::CreateFrom(*entry)); 930 base::MessageLoop::current()->PostTask( 931 FROM_HERE, 932 base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry))); 933 return CancelCallback(); 934} 935 936CancelCallback FakeDriveService::AddResourceToDirectory( 937 const std::string& parent_resource_id, 938 const std::string& resource_id, 939 const EntryActionCallback& callback) { 940 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 941 DCHECK(!callback.is_null()); 942 943 if (offline_) { 944 base::MessageLoop::current()->PostTask( 945 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION)); 946 return CancelCallback(); 947 } 948 949 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 950 if (entry) { 951 base::ListValue* links = NULL; 952 if (!entry->GetList("link", &links)) { 953 links = new base::ListValue; 954 entry->Set("link", links); 955 } 956 957 // On the real Drive server, resources do not necessary shape a tree 958 // structure. That is, each resource can have multiple parent. 959 // We mimic the behavior here; AddResourceToDirectoy just adds 960 // one more parent link, not overwriting old links. 961 base::DictionaryValue* link = new base::DictionaryValue; 962 link->SetString("rel", "http://schemas.google.com/docs/2007#parent"); 963 link->SetString( 964 "href", GetFakeLinkUrl(parent_resource_id).spec()); 965 links->Append(link); 966 967 AddNewChangestampAndETag(entry); 968 base::MessageLoop::current()->PostTask( 969 FROM_HERE, base::Bind(callback, HTTP_SUCCESS)); 970 return CancelCallback(); 971 } 972 973 base::MessageLoop::current()->PostTask( 974 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); 975 return CancelCallback(); 976} 977 978CancelCallback FakeDriveService::RemoveResourceFromDirectory( 979 const std::string& parent_resource_id, 980 const std::string& resource_id, 981 const EntryActionCallback& callback) { 982 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 983 DCHECK(!callback.is_null()); 984 985 if (offline_) { 986 base::MessageLoop::current()->PostTask( 987 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION)); 988 return CancelCallback(); 989 } 990 991 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 992 if (entry) { 993 base::ListValue* links = NULL; 994 if (entry->GetList("link", &links)) { 995 GURL parent_content_url = GetFakeLinkUrl(parent_resource_id); 996 for (size_t i = 0; i < links->GetSize(); ++i) { 997 base::DictionaryValue* link = NULL; 998 std::string rel; 999 std::string href; 1000 if (links->GetDictionary(i, &link) && 1001 link->GetString("rel", &rel) && 1002 link->GetString("href", &href) && 1003 rel == "http://schemas.google.com/docs/2007#parent" && 1004 GURL(href) == parent_content_url) { 1005 links->Remove(i, NULL); 1006 AddNewChangestampAndETag(entry); 1007 base::MessageLoop::current()->PostTask( 1008 FROM_HERE, base::Bind(callback, HTTP_SUCCESS)); 1009 return CancelCallback(); 1010 } 1011 } 1012 } 1013 } 1014 1015 base::MessageLoop::current()->PostTask( 1016 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); 1017 return CancelCallback(); 1018} 1019 1020CancelCallback FakeDriveService::AddNewDirectory( 1021 const std::string& parent_resource_id, 1022 const std::string& directory_title, 1023 const GetResourceEntryCallback& callback) { 1024 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1025 DCHECK(!callback.is_null()); 1026 1027 if (offline_) { 1028 scoped_ptr<ResourceEntry> null; 1029 base::MessageLoop::current()->PostTask( 1030 FROM_HERE, 1031 base::Bind(callback, 1032 GDATA_NO_CONNECTION, 1033 base::Passed(&null))); 1034 return CancelCallback(); 1035 } 1036 1037 const char kContentType[] = "application/atom+xml;type=feed"; 1038 const base::DictionaryValue* new_entry = AddNewEntry(kContentType, 1039 "", // content_data 1040 parent_resource_id, 1041 directory_title, 1042 false, // shared_with_me 1043 "folder"); 1044 if (!new_entry) { 1045 scoped_ptr<ResourceEntry> null; 1046 base::MessageLoop::current()->PostTask( 1047 FROM_HERE, 1048 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null))); 1049 return CancelCallback(); 1050 } 1051 1052 scoped_ptr<ResourceEntry> parsed_entry(ResourceEntry::CreateFrom(*new_entry)); 1053 base::MessageLoop::current()->PostTask( 1054 FROM_HERE, 1055 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry))); 1056 return CancelCallback(); 1057} 1058 1059CancelCallback FakeDriveService::InitiateUploadNewFile( 1060 const std::string& content_type, 1061 int64 content_length, 1062 const std::string& parent_resource_id, 1063 const std::string& title, 1064 const InitiateUploadCallback& callback) { 1065 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1066 DCHECK(!callback.is_null()); 1067 1068 if (offline_) { 1069 base::MessageLoop::current()->PostTask( 1070 FROM_HERE, 1071 base::Bind(callback, GDATA_NO_CONNECTION, GURL())); 1072 return CancelCallback(); 1073 } 1074 1075 if (parent_resource_id != GetRootResourceId() && 1076 !FindEntryByResourceId(parent_resource_id)) { 1077 base::MessageLoop::current()->PostTask( 1078 FROM_HERE, 1079 base::Bind(callback, HTTP_NOT_FOUND, GURL())); 1080 return CancelCallback(); 1081 } 1082 1083 GURL session_url = GetNewUploadSessionUrl(); 1084 upload_sessions_[session_url] = 1085 UploadSession(content_type, content_length, 1086 parent_resource_id, 1087 "", // resource_id 1088 "", // etag 1089 title); 1090 1091 base::MessageLoop::current()->PostTask( 1092 FROM_HERE, 1093 base::Bind(callback, HTTP_SUCCESS, session_url)); 1094 return CancelCallback(); 1095} 1096 1097CancelCallback FakeDriveService::InitiateUploadExistingFile( 1098 const std::string& content_type, 1099 int64 content_length, 1100 const std::string& resource_id, 1101 const std::string& etag, 1102 const InitiateUploadCallback& callback) { 1103 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1104 DCHECK(!callback.is_null()); 1105 1106 if (offline_) { 1107 base::MessageLoop::current()->PostTask( 1108 FROM_HERE, 1109 base::Bind(callback, GDATA_NO_CONNECTION, GURL())); 1110 return CancelCallback(); 1111 } 1112 1113 DictionaryValue* entry = FindEntryByResourceId(resource_id); 1114 if (!entry) { 1115 base::MessageLoop::current()->PostTask( 1116 FROM_HERE, 1117 base::Bind(callback, HTTP_NOT_FOUND, GURL())); 1118 return CancelCallback(); 1119 } 1120 1121 std::string entry_etag; 1122 entry->GetString("gd$etag", &entry_etag); 1123 if (!etag.empty() && etag != entry_etag) { 1124 base::MessageLoop::current()->PostTask( 1125 FROM_HERE, 1126 base::Bind(callback, HTTP_PRECONDITION, GURL())); 1127 return CancelCallback(); 1128 } 1129 1130 GURL session_url = GetNewUploadSessionUrl(); 1131 upload_sessions_[session_url] = 1132 UploadSession(content_type, content_length, 1133 "", // parent_resource_id 1134 resource_id, 1135 entry_etag, 1136 "" /* title */); 1137 1138 base::MessageLoop::current()->PostTask( 1139 FROM_HERE, 1140 base::Bind(callback, HTTP_SUCCESS, session_url)); 1141 return CancelCallback(); 1142} 1143 1144CancelCallback FakeDriveService::GetUploadStatus( 1145 const GURL& upload_url, 1146 int64 content_length, 1147 const UploadRangeCallback& callback) { 1148 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1149 DCHECK(!callback.is_null()); 1150 return CancelCallback(); 1151} 1152 1153CancelCallback FakeDriveService::ResumeUpload( 1154 const GURL& upload_url, 1155 int64 start_position, 1156 int64 end_position, 1157 int64 content_length, 1158 const std::string& content_type, 1159 const base::FilePath& local_file_path, 1160 const UploadRangeCallback& callback, 1161 const ProgressCallback& progress_callback) { 1162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1163 DCHECK(!callback.is_null()); 1164 1165 GetResourceEntryCallback completion_callback 1166 = base::Bind(&ScheduleUploadRangeCallback, 1167 callback, start_position, end_position); 1168 1169 if (offline_) { 1170 completion_callback.Run(GDATA_NO_CONNECTION, scoped_ptr<ResourceEntry>()); 1171 return CancelCallback(); 1172 } 1173 1174 if (!upload_sessions_.count(upload_url)) { 1175 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>()); 1176 return CancelCallback(); 1177 } 1178 1179 UploadSession* session = &upload_sessions_[upload_url]; 1180 1181 // Chunks are required to be sent in such a ways that they fill from the start 1182 // of the not-yet-uploaded part with no gaps nor overlaps. 1183 if (session->uploaded_size != start_position) { 1184 completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<ResourceEntry>()); 1185 return CancelCallback(); 1186 } 1187 1188 if (!progress_callback.is_null()) { 1189 // In the real GDataWapi/Drive DriveService, progress is reported in 1190 // nondeterministic timing. In this fake implementation, we choose to call 1191 // it twice per one ResumeUpload. This is for making sure that client code 1192 // works fine even if the callback is invoked more than once; it is the 1193 // crucial difference of the progress callback from others. 1194 // Note that progress is notified in the relative offset in each chunk. 1195 const int64 chunk_size = end_position - start_position; 1196 base::MessageLoop::current()->PostTask( 1197 FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size)); 1198 base::MessageLoop::current()->PostTask( 1199 FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size)); 1200 } 1201 1202 if (content_length != end_position) { 1203 session->uploaded_size = end_position; 1204 completion_callback.Run(HTTP_RESUME_INCOMPLETE, 1205 scoped_ptr<ResourceEntry>()); 1206 return CancelCallback(); 1207 } 1208 1209 std::string content_data; 1210 if (!file_util::ReadFileToString(local_file_path, &content_data)) { 1211 session->uploaded_size = end_position; 1212 completion_callback.Run(GDATA_FILE_ERROR, scoped_ptr<ResourceEntry>()); 1213 return CancelCallback(); 1214 } 1215 session->uploaded_size = end_position; 1216 1217 // |resource_id| is empty if the upload is for new file. 1218 if (session->resource_id.empty()) { 1219 DCHECK(!session->parent_resource_id.empty()); 1220 DCHECK(!session->title.empty()); 1221 const DictionaryValue* new_entry = AddNewEntry( 1222 session->content_type, 1223 content_data, 1224 session->parent_resource_id, 1225 session->title, 1226 false, // shared_with_me 1227 "file"); 1228 if (!new_entry) { 1229 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>()); 1230 return CancelCallback(); 1231 } 1232 1233 completion_callback.Run(HTTP_CREATED, 1234 ResourceEntry::CreateFrom(*new_entry)); 1235 return CancelCallback(); 1236 } 1237 1238 DictionaryValue* entry = FindEntryByResourceId(session->resource_id); 1239 if (!entry) { 1240 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>()); 1241 return CancelCallback(); 1242 } 1243 1244 std::string entry_etag; 1245 entry->GetString("gd$etag", &entry_etag); 1246 if (entry_etag.empty() || session->etag != entry_etag) { 1247 completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<ResourceEntry>()); 1248 return CancelCallback(); 1249 } 1250 1251 entry->SetString("docs$md5Checksum.$t", base::MD5String(content_data)); 1252 entry->Set("test$data", 1253 base::BinaryValue::CreateWithCopiedBuffer( 1254 content_data.data(), content_data.size())); 1255 entry->SetString("docs$size.$t", base::Int64ToString(end_position)); 1256 AddNewChangestampAndETag(entry); 1257 1258 completion_callback.Run(HTTP_SUCCESS, ResourceEntry::CreateFrom(*entry)); 1259 return CancelCallback(); 1260} 1261 1262CancelCallback FakeDriveService::AuthorizeApp( 1263 const std::string& resource_id, 1264 const std::string& app_id, 1265 const AuthorizeAppCallback& callback) { 1266 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1267 DCHECK(!callback.is_null()); 1268 return CancelCallback(); 1269} 1270 1271void FakeDriveService::AddNewFile(const std::string& content_type, 1272 const std::string& content_data, 1273 const std::string& parent_resource_id, 1274 const std::string& title, 1275 bool shared_with_me, 1276 const GetResourceEntryCallback& callback) { 1277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1278 DCHECK(!callback.is_null()); 1279 1280 if (offline_) { 1281 scoped_ptr<ResourceEntry> null; 1282 base::MessageLoop::current()->PostTask( 1283 FROM_HERE, 1284 base::Bind(callback, 1285 GDATA_NO_CONNECTION, 1286 base::Passed(&null))); 1287 return; 1288 } 1289 1290 // Prepare "kind" for hosted documents. This only supports Google Document. 1291 std::string entry_kind; 1292 if (content_type == "application/vnd.google-apps.document") 1293 entry_kind = "document"; 1294 else 1295 entry_kind = "file"; 1296 1297 const base::DictionaryValue* new_entry = AddNewEntry(content_type, 1298 content_data, 1299 parent_resource_id, 1300 title, 1301 shared_with_me, 1302 entry_kind); 1303 if (!new_entry) { 1304 scoped_ptr<ResourceEntry> null; 1305 base::MessageLoop::current()->PostTask( 1306 FROM_HERE, 1307 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null))); 1308 return; 1309 } 1310 1311 scoped_ptr<ResourceEntry> parsed_entry( 1312 ResourceEntry::CreateFrom(*new_entry)); 1313 base::MessageLoop::current()->PostTask( 1314 FROM_HERE, 1315 base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry))); 1316} 1317 1318void FakeDriveService::SetLastModifiedTime( 1319 const std::string& resource_id, 1320 const base::Time& last_modified_time, 1321 const GetResourceEntryCallback& callback) { 1322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1323 DCHECK(!callback.is_null()); 1324 1325 if (offline_) { 1326 scoped_ptr<ResourceEntry> null; 1327 base::MessageLoop::current()->PostTask( 1328 FROM_HERE, 1329 base::Bind(callback, 1330 GDATA_NO_CONNECTION, 1331 base::Passed(&null))); 1332 return; 1333 } 1334 1335 base::DictionaryValue* entry = FindEntryByResourceId(resource_id); 1336 if (!entry) { 1337 scoped_ptr<ResourceEntry> null; 1338 base::MessageLoop::current()->PostTask( 1339 FROM_HERE, 1340 base::Bind(callback, HTTP_NOT_FOUND, base::Passed(&null))); 1341 return; 1342 } 1343 1344 if (last_modified_time.is_null()) { 1345 entry->Remove("updated.$t", NULL); 1346 } else { 1347 entry->SetString("updated.$t", 1348 util::FormatTimeAsString(last_modified_time)); 1349 } 1350 1351 scoped_ptr<ResourceEntry> parsed_entry( 1352 ResourceEntry::CreateFrom(*entry)); 1353 base::MessageLoop::current()->PostTask( 1354 FROM_HERE, 1355 base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry))); 1356} 1357 1358base::DictionaryValue* FakeDriveService::FindEntryByResourceId( 1359 const std::string& resource_id) { 1360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1361 1362 base::ListValue* entries = NULL; 1363 // Go through entries and return the one that matches |resource_id|. 1364 if (resource_list_value_->GetList("entry", &entries)) { 1365 for (size_t i = 0; i < entries->GetSize(); ++i) { 1366 base::DictionaryValue* entry = NULL; 1367 std::string current_resource_id; 1368 if (entries->GetDictionary(i, &entry) && 1369 entry->GetString("gd$resourceId.$t", ¤t_resource_id) && 1370 resource_id == current_resource_id) { 1371 return entry; 1372 } 1373 } 1374 } 1375 1376 return NULL; 1377} 1378 1379base::DictionaryValue* FakeDriveService::FindEntryByContentUrl( 1380 const GURL& content_url) { 1381 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1382 1383 base::ListValue* entries = NULL; 1384 // Go through entries and return the one that matches |content_url|. 1385 if (resource_list_value_->GetList("entry", &entries)) { 1386 for (size_t i = 0; i < entries->GetSize(); ++i) { 1387 base::DictionaryValue* entry = NULL; 1388 std::string current_content_url; 1389 if (entries->GetDictionary(i, &entry) && 1390 entry->GetString("content.src", ¤t_content_url) && 1391 content_url == GURL(current_content_url)) { 1392 return entry; 1393 } 1394 } 1395 } 1396 1397 return NULL; 1398} 1399 1400std::string FakeDriveService::GetNewResourceId() { 1401 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1402 1403 ++resource_id_count_; 1404 return base::StringPrintf("resource_id_%d", resource_id_count_); 1405} 1406 1407void FakeDriveService::AddNewChangestampAndETag(base::DictionaryValue* entry) { 1408 ++largest_changestamp_; 1409 entry->SetString("docs$changestamp.value", 1410 base::Int64ToString(largest_changestamp_)); 1411 entry->SetString("gd$etag", 1412 "etag_" + base::Int64ToString(largest_changestamp_)); 1413} 1414 1415const base::DictionaryValue* FakeDriveService::AddNewEntry( 1416 const std::string& content_type, 1417 const std::string& content_data, 1418 const std::string& parent_resource_id, 1419 const std::string& title, 1420 bool shared_with_me, 1421 const std::string& entry_kind) { 1422 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1423 1424 if (parent_resource_id != GetRootResourceId() && 1425 !FindEntryByResourceId(parent_resource_id)) { 1426 return NULL; 1427 } 1428 1429 std::string resource_id = GetNewResourceId(); 1430 GURL upload_url = GURL("https://xxx/upload/" + resource_id); 1431 1432 scoped_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue); 1433 // Set the resource ID and the title 1434 new_entry->SetString("gd$resourceId.$t", resource_id); 1435 new_entry->SetString("title.$t", title); 1436 new_entry->SetString("docs$filename", title); 1437 // Set the contents, size and MD5 for a file. 1438 if (entry_kind == "file") { 1439 new_entry->Set("test$data", 1440 base::BinaryValue::CreateWithCopiedBuffer( 1441 content_data.c_str(), content_data.size())); 1442 new_entry->SetString("docs$size.$t", 1443 base::Int64ToString(content_data.size())); 1444 new_entry->SetString("docs$md5Checksum.$t", 1445 base::MD5String(content_data)); 1446 } 1447 1448 // Add "category" which sets the resource type to |entry_kind|. 1449 base::ListValue* categories = new base::ListValue; 1450 base::DictionaryValue* category = new base::DictionaryValue; 1451 category->SetString("scheme", "http://schemas.google.com/g/2005#kind"); 1452 category->SetString("term", "http://schemas.google.com/docs/2007#" + 1453 entry_kind); 1454 categories->Append(category); 1455 new_entry->Set("category", categories); 1456 if (shared_with_me) { 1457 base::DictionaryValue* shared_with_me_label = new base::DictionaryValue; 1458 shared_with_me_label->SetString("label", "shared-with-me"); 1459 shared_with_me_label->SetString("scheme", 1460 "http://schemas.google.com/g/2005/labels"); 1461 shared_with_me_label->SetString( 1462 "term", "http://schemas.google.com/g/2005/labels#shared"); 1463 categories->Append(shared_with_me_label); 1464 } 1465 1466 std::string escaped_resource_id = net::EscapePath(resource_id); 1467 1468 // Add "content" which sets the content URL. 1469 base::DictionaryValue* content = new base::DictionaryValue; 1470 content->SetString("src", "https://xxx/content/" + escaped_resource_id); 1471 content->SetString("type", content_type); 1472 new_entry->Set("content", content); 1473 1474 // Add "link" which sets the parent URL, the edit URL and the upload URL. 1475 base::ListValue* links = new base::ListValue; 1476 1477 base::DictionaryValue* parent_link = new base::DictionaryValue; 1478 parent_link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec()); 1479 parent_link->SetString("rel", 1480 "http://schemas.google.com/docs/2007#parent"); 1481 links->Append(parent_link); 1482 1483 base::DictionaryValue* edit_link = new base::DictionaryValue; 1484 edit_link->SetString("href", "https://xxx/edit/" + escaped_resource_id); 1485 edit_link->SetString("rel", "edit"); 1486 links->Append(edit_link); 1487 1488 base::DictionaryValue* upload_link = new base::DictionaryValue; 1489 upload_link->SetString("href", upload_url.spec()); 1490 upload_link->SetString("rel", kUploadUrlRel); 1491 links->Append(upload_link); 1492 1493 const GURL share_url = net::AppendOrReplaceQueryParameter( 1494 share_url_base_, "name", title); 1495 base::DictionaryValue* share_link = new base::DictionaryValue; 1496 upload_link->SetString("href", share_url.spec()); 1497 upload_link->SetString("rel", kShareUrlRel); 1498 links->Append(share_link); 1499 new_entry->Set("link", links); 1500 1501 AddNewChangestampAndETag(new_entry.get()); 1502 1503 base::Time published_date = 1504 base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_); 1505 new_entry->SetString("published.$t", 1506 util::FormatTimeAsString(published_date)); 1507 1508 // If there are no entries, prepare an empty entry to add. 1509 if (!resource_list_value_->HasKey("entry")) 1510 resource_list_value_->Set("entry", new ListValue); 1511 1512 base::DictionaryValue* raw_new_entry = new_entry.release(); 1513 base::ListValue* entries = NULL; 1514 if (resource_list_value_->GetList("entry", &entries)) 1515 entries->Append(raw_new_entry); 1516 1517 return raw_new_entry; 1518} 1519 1520void FakeDriveService::GetResourceListInternal( 1521 int64 start_changestamp, 1522 const std::string& search_query, 1523 const std::string& directory_resource_id, 1524 int start_offset, 1525 int max_results, 1526 int* load_counter, 1527 const GetResourceListCallback& callback) { 1528 if (offline_) { 1529 base::MessageLoop::current()->PostTask( 1530 FROM_HERE, 1531 base::Bind(callback, 1532 GDATA_NO_CONNECTION, 1533 base::Passed(scoped_ptr<ResourceList>()))); 1534 return; 1535 } 1536 1537 scoped_ptr<ResourceList> resource_list = 1538 ResourceList::CreateFrom(*resource_list_value_); 1539 1540 // TODO(hashimoto): Drive API always provides largest changestamp. Remove this 1541 // if-statement after API switch. 1542 if (start_changestamp > 0 && start_offset == 0) 1543 resource_list->set_largest_changestamp(largest_changestamp_); 1544 1545 // Filter out entries per parameters like |directory_resource_id| and 1546 // |search_query|. 1547 ScopedVector<ResourceEntry>* entries = resource_list->mutable_entries(); 1548 1549 int num_entries_matched = 0; 1550 for (size_t i = 0; i < entries->size();) { 1551 ResourceEntry* entry = (*entries)[i]; 1552 bool should_exclude = false; 1553 1554 // If |directory_resource_id| is set, exclude the entry if it's not in 1555 // the target directory. 1556 if (!directory_resource_id.empty()) { 1557 // Get the parent resource ID of the entry. 1558 std::string parent_resource_id; 1559 const google_apis::Link* parent_link = 1560 entry->GetLinkByType(Link::LINK_PARENT); 1561 if (parent_link) { 1562 parent_resource_id = 1563 net::UnescapeURLComponent(parent_link->href().ExtractFileName(), 1564 net::UnescapeRule::URL_SPECIAL_CHARS); 1565 } 1566 if (directory_resource_id != parent_resource_id) 1567 should_exclude = true; 1568 } 1569 1570 // If |search_query| is set, exclude the entry if it does not contain the 1571 // search query in the title. 1572 if (!should_exclude && !search_query.empty() && 1573 !EntryMatchWithQuery(*entry, search_query)) { 1574 should_exclude = true; 1575 } 1576 1577 // If |start_changestamp| is set, exclude the entry if the 1578 // changestamp is older than |largest_changestamp|. 1579 // See https://developers.google.com/google-apps/documents-list/ 1580 // #retrieving_all_changes_since_a_given_changestamp 1581 if (start_changestamp > 0 && entry->changestamp() < start_changestamp) 1582 should_exclude = true; 1583 1584 // If the caller requests other list than change list by specifying 1585 // zero-|start_changestamp|, exclude deleted entry from the result. 1586 if (!start_changestamp && entry->deleted()) 1587 should_exclude = true; 1588 1589 // The entry matched the criteria for inclusion. 1590 if (!should_exclude) 1591 ++num_entries_matched; 1592 1593 // If |start_offset| is set, exclude the entry if the entry is before the 1594 // start index. <= instead of < as |num_entries_matched| was 1595 // already incremented. 1596 if (start_offset > 0 && num_entries_matched <= start_offset) 1597 should_exclude = true; 1598 1599 if (should_exclude) 1600 entries->erase(entries->begin() + i); 1601 else 1602 ++i; 1603 } 1604 1605 // If |max_results| is set, trim the entries if the number exceeded the max 1606 // results. 1607 if (max_results > 0 && entries->size() > static_cast<size_t>(max_results)) { 1608 entries->erase(entries->begin() + max_results, entries->end()); 1609 // Adds the next URL. 1610 // Here, we embed information which is needed for continuing the 1611 // GetResourceList request in the next invocation into url query 1612 // parameters. 1613 GURL next_url(base::StringPrintf( 1614 "http://localhost/?start-offset=%d&max-results=%d", 1615 start_offset + max_results, 1616 max_results)); 1617 if (start_changestamp > 0) { 1618 next_url = net::AppendOrReplaceQueryParameter( 1619 next_url, "changestamp", 1620 base::Int64ToString(start_changestamp).c_str()); 1621 } 1622 if (!search_query.empty()) { 1623 next_url = net::AppendOrReplaceQueryParameter( 1624 next_url, "q", search_query); 1625 } 1626 if (!directory_resource_id.empty()) { 1627 next_url = net::AppendOrReplaceQueryParameter( 1628 next_url, "parent", directory_resource_id); 1629 } 1630 1631 Link* link = new Link; 1632 link->set_type(Link::LINK_NEXT); 1633 link->set_href(next_url); 1634 resource_list->mutable_links()->push_back(link); 1635 } 1636 1637 if (load_counter) 1638 *load_counter += 1; 1639 base::MessageLoop::current()->PostTask( 1640 FROM_HERE, 1641 base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_list))); 1642} 1643 1644GURL FakeDriveService::GetNewUploadSessionUrl() { 1645 return GURL("https://upload_session_url/" + 1646 base::Int64ToString(next_upload_sequence_number_++)); 1647} 1648 1649} // namespace drive 1650