directory_loader.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
1// Copyright 2014 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/chromeos/drive/directory_loader.h" 6 7#include "base/callback.h" 8#include "base/callback_helpers.h" 9#include "base/metrics/histogram.h" 10#include "base/strings/string_number_conversions.h" 11#include "base/time/time.h" 12#include "chrome/browser/chromeos/drive/change_list_loader.h" 13#include "chrome/browser/chromeos/drive/change_list_loader_observer.h" 14#include "chrome/browser/chromeos/drive/change_list_processor.h" 15#include "chrome/browser/chromeos/drive/file_system_util.h" 16#include "chrome/browser/chromeos/drive/job_scheduler.h" 17#include "chrome/browser/chromeos/drive/resource_metadata.h" 18#include "chrome/browser/drive/event_logger.h" 19#include "content/public/browser/browser_thread.h" 20#include "google_apis/drive/drive_api_parser.h" 21#include "url/gurl.h" 22 23using content::BrowserThread; 24 25namespace drive { 26namespace internal { 27 28namespace { 29 30// Minimum changestamp gap required to start loading directory. 31const int kMinimumChangestampGap = 50; 32 33FileError CheckLocalState(ResourceMetadata* resource_metadata, 34 const google_apis::AboutResource& about_resource, 35 const std::string& local_id, 36 ResourceEntry* entry, 37 int64* local_changestamp) { 38 // Fill My Drive resource ID. 39 ResourceEntry mydrive; 40 FileError error = resource_metadata->GetResourceEntryByPath( 41 util::GetDriveMyDriveRootPath(), &mydrive); 42 if (error != FILE_ERROR_OK) 43 return error; 44 45 if (mydrive.resource_id().empty()) { 46 mydrive.set_resource_id(about_resource.root_folder_id()); 47 error = resource_metadata->RefreshEntry(mydrive); 48 if (error != FILE_ERROR_OK) 49 return error; 50 } 51 52 // Get entry. 53 error = resource_metadata->GetResourceEntryById(local_id, entry); 54 if (error != FILE_ERROR_OK) 55 return error; 56 57 // Get the local changestamp. 58 *local_changestamp = resource_metadata->GetLargestChangestamp(); 59 return FILE_ERROR_OK; 60} 61 62FileError UpdateChangestamp(ResourceMetadata* resource_metadata, 63 const DirectoryFetchInfo& directory_fetch_info, 64 base::FilePath* directory_path) { 65 // Update the directory changestamp. 66 ResourceEntry directory; 67 FileError error = resource_metadata->GetResourceEntryById( 68 directory_fetch_info.local_id(), &directory); 69 if (error != FILE_ERROR_OK) 70 return error; 71 72 if (!directory.file_info().is_directory()) 73 return FILE_ERROR_NOT_A_DIRECTORY; 74 75 directory.mutable_directory_specific_info()->set_changestamp( 76 directory_fetch_info.changestamp()); 77 error = resource_metadata->RefreshEntry(directory); 78 if (error != FILE_ERROR_OK) 79 return error; 80 81 // Get the directory path. 82 *directory_path = resource_metadata->GetFilePath( 83 directory_fetch_info.local_id()); 84 return FILE_ERROR_OK; 85} 86 87} // namespace 88 89struct DirectoryLoader::ReadDirectoryCallbackState { 90 ReadDirectoryEntriesCallback entries_callback; 91 FileOperationCallback completion_callback; 92 std::set<std::string> sent_entry_names; 93}; 94 95// Fetches the resource entries in the directory with |directory_resource_id|. 96class DirectoryLoader::FeedFetcher { 97 public: 98 FeedFetcher(DirectoryLoader* loader, 99 const DirectoryFetchInfo& directory_fetch_info, 100 const std::string& root_folder_id) 101 : loader_(loader), 102 directory_fetch_info_(directory_fetch_info), 103 root_folder_id_(root_folder_id), 104 weak_ptr_factory_(this) { 105 } 106 107 ~FeedFetcher() { 108 } 109 110 void Run(const FileOperationCallback& callback) { 111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 112 DCHECK(!callback.is_null()); 113 DCHECK(!directory_fetch_info_.resource_id().empty()); 114 115 // Remember the time stamp for usage stats. 116 start_time_ = base::TimeTicks::Now(); 117 118 loader_->scheduler_->GetResourceListInDirectory( 119 directory_fetch_info_.resource_id(), 120 base::Bind(&FeedFetcher::OnResourceListFetched, 121 weak_ptr_factory_.GetWeakPtr(), callback)); 122 } 123 124 private: 125 void OnResourceListFetched( 126 const FileOperationCallback& callback, 127 google_apis::GDataErrorCode status, 128 scoped_ptr<google_apis::ResourceList> resource_list) { 129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 130 DCHECK(!callback.is_null()); 131 132 FileError error = GDataToFileError(status); 133 if (error != FILE_ERROR_OK) { 134 callback.Run(error); 135 return; 136 } 137 138 DCHECK(resource_list); 139 scoped_ptr<ChangeList> change_list(new ChangeList(*resource_list)); 140 141 GURL next_url; 142 resource_list->GetNextFeedURL(&next_url); 143 144 ResourceEntryVector* entries = new ResourceEntryVector; 145 loader_->loader_controller_->ScheduleRun(base::Bind( 146 base::IgnoreResult( 147 &base::PostTaskAndReplyWithResult<FileError, FileError>), 148 loader_->blocking_task_runner_, 149 FROM_HERE, 150 base::Bind(&ChangeListProcessor::RefreshDirectory, 151 loader_->resource_metadata_, 152 directory_fetch_info_, 153 base::Passed(&change_list), 154 entries), 155 base::Bind(&FeedFetcher::OnDirectoryRefreshed, 156 weak_ptr_factory_.GetWeakPtr(), 157 callback, 158 next_url, 159 base::Owned(entries)))); 160 } 161 162 void OnDirectoryRefreshed( 163 const FileOperationCallback& callback, 164 const GURL& next_url, 165 const std::vector<ResourceEntry>* refreshed_entries, 166 FileError error) { 167 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 168 DCHECK(!callback.is_null()); 169 170 if (error != FILE_ERROR_OK) { 171 callback.Run(error); 172 return; 173 } 174 175 loader_->SendEntries(directory_fetch_info_.local_id(), *refreshed_entries); 176 177 if (!next_url.is_empty()) { 178 // There is the remaining result so fetch it. 179 loader_->scheduler_->GetRemainingFileList( 180 next_url, 181 base::Bind(&FeedFetcher::OnResourceListFetched, 182 weak_ptr_factory_.GetWeakPtr(), callback)); 183 return; 184 } 185 186 UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime", 187 base::TimeTicks::Now() - start_time_); 188 189 // Note: The fetcher is managed by DirectoryLoader, and the instance 190 // will be deleted in the callback. Do not touch the fields after this 191 // invocation. 192 callback.Run(FILE_ERROR_OK); 193 } 194 195 DirectoryLoader* loader_; 196 DirectoryFetchInfo directory_fetch_info_; 197 std::string root_folder_id_; 198 base::TimeTicks start_time_; 199 base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_; 200 DISALLOW_COPY_AND_ASSIGN(FeedFetcher); 201}; 202 203DirectoryLoader::DirectoryLoader( 204 EventLogger* logger, 205 base::SequencedTaskRunner* blocking_task_runner, 206 ResourceMetadata* resource_metadata, 207 JobScheduler* scheduler, 208 AboutResourceLoader* about_resource_loader, 209 LoaderController* loader_controller) 210 : logger_(logger), 211 blocking_task_runner_(blocking_task_runner), 212 resource_metadata_(resource_metadata), 213 scheduler_(scheduler), 214 about_resource_loader_(about_resource_loader), 215 loader_controller_(loader_controller), 216 weak_ptr_factory_(this) { 217} 218 219DirectoryLoader::~DirectoryLoader() { 220 STLDeleteElements(&fast_fetch_feed_fetcher_set_); 221} 222 223void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) { 224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 225 observers_.AddObserver(observer); 226} 227 228void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) { 229 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 230 observers_.RemoveObserver(observer); 231} 232 233void DirectoryLoader::ReadDirectory( 234 const base::FilePath& directory_path, 235 const ReadDirectoryEntriesCallback& entries_callback, 236 const FileOperationCallback& completion_callback) { 237 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 238 DCHECK(!completion_callback.is_null()); 239 240 ResourceEntry* entry = new ResourceEntry; 241 base::PostTaskAndReplyWithResult( 242 blocking_task_runner_.get(), 243 FROM_HERE, 244 base::Bind(&ResourceMetadata::GetResourceEntryByPath, 245 base::Unretained(resource_metadata_), 246 directory_path, 247 entry), 248 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry, 249 weak_ptr_factory_.GetWeakPtr(), 250 directory_path, 251 entries_callback, 252 completion_callback, 253 true, // should_try_loading_parent 254 base::Owned(entry))); 255} 256 257void DirectoryLoader::ReadDirectoryAfterGetEntry( 258 const base::FilePath& directory_path, 259 const ReadDirectoryEntriesCallback& entries_callback, 260 const FileOperationCallback& completion_callback, 261 bool should_try_loading_parent, 262 const ResourceEntry* entry, 263 FileError error) { 264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 265 DCHECK(!completion_callback.is_null()); 266 267 if (error == FILE_ERROR_NOT_FOUND && 268 should_try_loading_parent && 269 util::GetDriveGrandRootPath().IsParent(directory_path)) { 270 // This entry may be found after loading the parent. 271 ReadDirectory(directory_path.DirName(), 272 ReadDirectoryEntriesCallback(), 273 base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent, 274 weak_ptr_factory_.GetWeakPtr(), 275 directory_path, 276 entries_callback, 277 completion_callback)); 278 return; 279 } 280 if (error != FILE_ERROR_OK) { 281 completion_callback.Run(error); 282 return; 283 } 284 285 if (!entry->file_info().is_directory()) { 286 completion_callback.Run(FILE_ERROR_NOT_A_DIRECTORY); 287 return; 288 } 289 290 DirectoryFetchInfo directory_fetch_info( 291 entry->local_id(), 292 entry->resource_id(), 293 entry->directory_specific_info().changestamp()); 294 295 // Register the callback function to be called when it is loaded. 296 const std::string& local_id = directory_fetch_info.local_id(); 297 ReadDirectoryCallbackState callback_state; 298 callback_state.entries_callback = entries_callback; 299 callback_state.completion_callback = completion_callback; 300 pending_load_callback_[local_id].push_back(callback_state); 301 302 // If loading task for |local_id| is already running, do nothing. 303 if (pending_load_callback_[local_id].size() > 1) 304 return; 305 306 // Note: To be precise, we need to call UpdateAboutResource() here. However, 307 // - It is costly to do GetAboutResource HTTP request every time. 308 // - The chance using an old value is small; it only happens when 309 // ReadDirectory is called during one GetAboutResource roundtrip time 310 // of a change list fetching. 311 // - Even if the value is old, it just marks the directory as older. It may 312 // trigger one future unnecessary re-fetch, but it'll never lose data. 313 about_resource_loader_->GetAboutResource( 314 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource, 315 weak_ptr_factory_.GetWeakPtr(), local_id)); 316} 317 318void DirectoryLoader::ReadDirectoryAfterLoadParent( 319 const base::FilePath& directory_path, 320 const ReadDirectoryEntriesCallback& entries_callback, 321 const FileOperationCallback& completion_callback, 322 FileError error) { 323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 324 DCHECK(!completion_callback.is_null()); 325 326 if (error != FILE_ERROR_OK) { 327 completion_callback.Run(error); 328 return; 329 } 330 331 ResourceEntry* entry = new ResourceEntry; 332 base::PostTaskAndReplyWithResult( 333 blocking_task_runner_.get(), 334 FROM_HERE, 335 base::Bind(&ResourceMetadata::GetResourceEntryByPath, 336 base::Unretained(resource_metadata_), 337 directory_path, 338 entry), 339 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry, 340 weak_ptr_factory_.GetWeakPtr(), 341 directory_path, 342 entries_callback, 343 completion_callback, 344 false, // should_try_loading_parent 345 base::Owned(entry))); 346} 347 348void DirectoryLoader::ReadDirectoryAfterGetAboutResource( 349 const std::string& local_id, 350 google_apis::GDataErrorCode status, 351 scoped_ptr<google_apis::AboutResource> about_resource) { 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 353 354 FileError error = GDataToFileError(status); 355 if (error != FILE_ERROR_OK) { 356 OnDirectoryLoadComplete(local_id, error); 357 return; 358 } 359 360 DCHECK(about_resource); 361 362 // Check the current status of local metadata, and start loading if needed. 363 google_apis::AboutResource* about_resource_ptr = about_resource.get(); 364 ResourceEntry* entry = new ResourceEntry; 365 int64* local_changestamp = new int64; 366 base::PostTaskAndReplyWithResult( 367 blocking_task_runner_, 368 FROM_HERE, 369 base::Bind(&CheckLocalState, 370 resource_metadata_, 371 *about_resource_ptr, 372 local_id, 373 entry, 374 local_changestamp), 375 base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState, 376 weak_ptr_factory_.GetWeakPtr(), 377 base::Passed(&about_resource), 378 local_id, 379 base::Owned(entry), 380 base::Owned(local_changestamp))); 381} 382 383void DirectoryLoader::ReadDirectoryAfterCheckLocalState( 384 scoped_ptr<google_apis::AboutResource> about_resource, 385 const std::string& local_id, 386 const ResourceEntry* entry, 387 const int64* local_changestamp, 388 FileError error) { 389 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 390 DCHECK(about_resource); 391 392 if (error != FILE_ERROR_OK) { 393 OnDirectoryLoadComplete(local_id, error); 394 return; 395 } 396 // This entry does not exist on the server. 397 if (entry->resource_id().empty()) { 398 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK); 399 return; 400 } 401 402 int64 remote_changestamp = about_resource->largest_change_id(); 403 404 // Start loading the directory. 405 int64 directory_changestamp = std::max( 406 entry->directory_specific_info().changestamp(), *local_changestamp); 407 408 DirectoryFetchInfo directory_fetch_info( 409 local_id, entry->resource_id(), remote_changestamp); 410 411 // If the directory's changestamp is new enough, just schedule to run the 412 // callback, as there is no need to fetch the directory. 413 if (directory_changestamp + kMinimumChangestampGap > remote_changestamp) { 414 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK); 415 } else { 416 // Start fetching the directory content, and mark it with the changestamp 417 // |remote_changestamp|. 418 LoadDirectoryFromServer(directory_fetch_info); 419 } 420} 421 422void DirectoryLoader::OnDirectoryLoadComplete(const std::string& local_id, 423 FileError error) { 424 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 425 426 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id); 427 if (it == pending_load_callback_.end()) 428 return; 429 430 // No need to read metadata when no one needs entries. 431 bool needs_to_send_entries = false; 432 for (size_t i = 0; i < it->second.size(); ++i) { 433 const ReadDirectoryCallbackState& callback_state = it->second[i]; 434 if (!callback_state.entries_callback.is_null()) 435 needs_to_send_entries = true; 436 } 437 438 if (!needs_to_send_entries) { 439 OnDirectoryLoadCompleteAfterRead(local_id, NULL, FILE_ERROR_OK); 440 return; 441 } 442 443 ResourceEntryVector* entries = new ResourceEntryVector; 444 base::PostTaskAndReplyWithResult( 445 blocking_task_runner_.get(), 446 FROM_HERE, 447 base::Bind(&ResourceMetadata::ReadDirectoryById, 448 base::Unretained(resource_metadata_), local_id, entries), 449 base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead, 450 weak_ptr_factory_.GetWeakPtr(), 451 local_id, 452 base::Owned(entries))); 453} 454 455void DirectoryLoader::OnDirectoryLoadCompleteAfterRead( 456 const std::string& local_id, 457 const ResourceEntryVector* entries, 458 FileError error) { 459 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id); 460 if (it != pending_load_callback_.end()) { 461 DVLOG(1) << "Running callback for " << local_id; 462 463 if (error == FILE_ERROR_OK && entries) 464 SendEntries(local_id, *entries); 465 466 for (size_t i = 0; i < it->second.size(); ++i) { 467 const ReadDirectoryCallbackState& callback_state = it->second[i]; 468 callback_state.completion_callback.Run(error); 469 } 470 pending_load_callback_.erase(it); 471 } 472} 473 474void DirectoryLoader::SendEntries(const std::string& local_id, 475 const ResourceEntryVector& entries) { 476 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id); 477 DCHECK(it != pending_load_callback_.end()); 478 479 for (size_t i = 0; i < it->second.size(); ++i) { 480 ReadDirectoryCallbackState* callback_state = &it->second[i]; 481 if (callback_state->entries_callback.is_null()) 482 continue; 483 484 // Filter out entries which were already sent. 485 scoped_ptr<ResourceEntryVector> entries_to_send(new ResourceEntryVector); 486 for (size_t i = 0; i < entries.size(); ++i) { 487 const ResourceEntry& entry = entries[i]; 488 if (!callback_state->sent_entry_names.count(entry.base_name())) { 489 callback_state->sent_entry_names.insert(entry.base_name()); 490 entries_to_send->push_back(entry); 491 } 492 } 493 callback_state->entries_callback.Run(entries_to_send.Pass()); 494 } 495} 496 497void DirectoryLoader::LoadDirectoryFromServer( 498 const DirectoryFetchInfo& directory_fetch_info) { 499 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 500 DCHECK(!directory_fetch_info.empty()); 501 DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString(); 502 503 const google_apis::AboutResource* about_resource = 504 about_resource_loader_->cached_about_resource(); 505 DCHECK(about_resource); 506 507 logger_->Log(logging::LOG_INFO, 508 "Fast-fetch start: %s; Server changestamp: %s", 509 directory_fetch_info.ToString().c_str(), 510 base::Int64ToString( 511 about_resource->largest_change_id()).c_str()); 512 513 FeedFetcher* fetcher = new FeedFetcher(this, 514 directory_fetch_info, 515 about_resource->root_folder_id()); 516 fast_fetch_feed_fetcher_set_.insert(fetcher); 517 fetcher->Run( 518 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad, 519 weak_ptr_factory_.GetWeakPtr(), 520 directory_fetch_info, 521 fetcher)); 522} 523 524void DirectoryLoader::LoadDirectoryFromServerAfterLoad( 525 const DirectoryFetchInfo& directory_fetch_info, 526 FeedFetcher* fetcher, 527 FileError error) { 528 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 529 DCHECK(!directory_fetch_info.empty()); 530 531 // Delete the fetcher. 532 fast_fetch_feed_fetcher_set_.erase(fetcher); 533 delete fetcher; 534 535 logger_->Log(logging::LOG_INFO, 536 "Fast-fetch complete: %s => %s", 537 directory_fetch_info.ToString().c_str(), 538 FileErrorToString(error).c_str()); 539 540 if (error != FILE_ERROR_OK) { 541 LOG(ERROR) << "Failed to load directory: " 542 << directory_fetch_info.local_id() 543 << ": " << FileErrorToString(error); 544 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error); 545 return; 546 } 547 548 // Update changestamp and get the directory path. 549 base::FilePath* directory_path = new base::FilePath; 550 base::PostTaskAndReplyWithResult( 551 blocking_task_runner_.get(), 552 FROM_HERE, 553 base::Bind(&UpdateChangestamp, 554 resource_metadata_, 555 directory_fetch_info, 556 directory_path), 557 base::Bind( 558 &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp, 559 weak_ptr_factory_.GetWeakPtr(), 560 directory_fetch_info, 561 base::Owned(directory_path))); 562} 563 564void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp( 565 const DirectoryFetchInfo& directory_fetch_info, 566 const base::FilePath* directory_path, 567 FileError error) { 568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 569 570 DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString(); 571 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error); 572 573 // Also notify the observers. 574 if (error == FILE_ERROR_OK && !directory_path->empty()) { 575 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, 576 OnDirectoryChanged(*directory_path)); 577 } 578} 579 580} // namespace internal 581} // namespace drive 582