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