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