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