change_list_loader.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/chromeos/drive/change_list_loader.h" 6 7#include <set> 8 9#include "base/callback.h" 10#include "base/metrics/histogram.h" 11#include "chrome/browser/chromeos/drive/change_list_loader_observer.h" 12#include "chrome/browser/chromeos/drive/change_list_processor.h" 13#include "chrome/browser/chromeos/drive/drive_file_system_util.h" 14#include "chrome/browser/chromeos/drive/drive_scheduler.h" 15#include "chrome/browser/chromeos/drive/drive_webapps_registry.h" 16#include "chrome/browser/google_apis/drive_api_parser.h" 17#include "chrome/browser/google_apis/drive_api_util.h" 18#include "content/public/browser/browser_thread.h" 19#include "googleurl/src/gurl.h" 20 21using content::BrowserThread; 22 23namespace drive { 24 25namespace { 26 27// Update the fetch progress UI per every this number of feeds. 28const int kFetchUiUpdateStep = 10; 29 30} // namespace 31 32// Set of parameters sent to LoadFromServer. 33// 34// Value of |start_changestamp| determines the type of feed to load - 0 means 35// root feed, every other value would trigger delta feed. 36// 37// Loaded feed may be partial due to size limit on a single feed. In that case, 38// the loaded feed will have next feed url set. Iff |load_subsequent_feeds| 39// parameter is set, feed loader will load all subsequent feeds. 40// 41// If invoked as a part of content search, query will be set in |search_query|. 42// If |feed_to_load| is set, this is feed url that will be used to load feed. 43// 44// When all feeds are loaded, |feed_load_callback| is invoked with the retrieved 45// feeds. |feed_load_callback| must not be null. 46struct ChangeListLoader::LoadFeedParams { 47 LoadFeedParams() 48 : start_changestamp(0), 49 shared_with_me(false), 50 load_subsequent_feeds(true) {} 51 52 // Changestamps are positive numbers in increasing order. The difference 53 // between two changestamps is proportional equal to number of items in 54 // delta feed between them - bigger the difference, more likely bigger 55 // number of items in delta feeds. 56 int64 start_changestamp; 57 std::string search_query; 58 bool shared_with_me; 59 std::string directory_resource_id; 60 GURL feed_to_load; 61 bool load_subsequent_feeds; 62 ScopedVector<google_apis::ResourceList> feed_list; 63 scoped_ptr<GetResourceListUiState> ui_state; 64}; 65 66// Defines set of parameters sent to callback OnNotifyResourceListFetched(). 67// This is a trick to update the number of fetched documents frequently on 68// UI. Due to performance reason, we need to fetch a number of files at 69// a time. However, it'll take long time, and a user has no way to know 70// the current update state. In order to make users comfortable, 71// we increment the number of fetched documents with more frequent but smaller 72// steps than actual fetching. 73struct ChangeListLoader::GetResourceListUiState { 74 explicit GetResourceListUiState(base::TimeTicks start_time) 75 : num_fetched_documents(0), 76 num_showing_documents(0), 77 start_time(start_time), 78 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory(this)) { 79 } 80 81 // The number of fetched documents. 82 int num_fetched_documents; 83 84 // The number documents shown on UI. 85 int num_showing_documents; 86 87 // When the UI update has started. 88 base::TimeTicks start_time; 89 90 // Time elapsed since the feed fetching was started. 91 base::TimeDelta feed_fetching_elapsed_time; 92 93 base::WeakPtrFactory<GetResourceListUiState> weak_ptr_factory; 94}; 95 96ChangeListLoader::ChangeListLoader(DriveResourceMetadata* resource_metadata, 97 DriveScheduler* scheduler, 98 DriveWebAppsRegistry* webapps_registry) 99 : resource_metadata_(resource_metadata), 100 scheduler_(scheduler), 101 webapps_registry_(webapps_registry), 102 refreshing_(false), 103 last_known_remote_changestamp_(0), 104 loaded_(false), 105 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { 106} 107 108ChangeListLoader::~ChangeListLoader() { 109} 110 111void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) { 112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 113 observers_.AddObserver(observer); 114} 115 116void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) { 117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 118 observers_.RemoveObserver(observer); 119} 120 121void ChangeListLoader::LoadFromServerIfNeeded( 122 const DirectoryFetchInfo& directory_fetch_info, 123 const FileOperationCallback& callback) { 124 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 125 DCHECK(!callback.is_null()); 126 127 // Sets the refreshing flag, so that the caller does not send refresh requests 128 // in parallel (see DriveFileSystem::CheckForUpdates). Corresponding 129 // "refresh_ = false" is in OnGetAboutResource when the cached feed is up to 130 // date, or in OnFeedFromServerLoaded called back from LoadFromServer(). 131 refreshing_ = true; 132 133 // Drive v2 needs a separate application list fetch operation. 134 // On GData WAPI, it is not necessary in theory, because the response 135 // of account metadata can include both about account information (such as 136 // quota) and an application list at once. 137 // However, for Drive API v2 migration, we connect to the server twice 138 // (one for about account information and another for an application list) 139 // regardless of underlying API, so that we can simplify the code. 140 // Note that the size of account metadata on GData WAPI seems small enough 141 // and (by controlling the query parameter) the response for GetAboutResource 142 // operation doesn't contain application list. Thus, the effect should be 143 // small cost. 144 // TODO(haruki): Application list rarely changes and is not necessarily 145 // refreshed as often as files. 146 scheduler_->GetAppList( 147 base::Bind(&ChangeListLoader::OnGetAppList, 148 weak_ptr_factory_.GetWeakPtr())); 149 150 // First fetch the latest changestamp to see if there were any new changes 151 // there at all. 152 scheduler_->GetAboutResource( 153 base::Bind(&ChangeListLoader::LoadFromServerIfNeededAfterGetAbout, 154 weak_ptr_factory_.GetWeakPtr(), 155 directory_fetch_info, 156 callback)); 157} 158 159void ChangeListLoader::LoadFromServerIfNeededAfterGetAbout( 160 const DirectoryFetchInfo& directory_fetch_info, 161 const FileOperationCallback& callback, 162 google_apis::GDataErrorCode status, 163 scoped_ptr<google_apis::AboutResource> about_resource) { 164 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 165 DCHECK(!callback.is_null()); 166 DCHECK(refreshing_); 167 DCHECK_EQ(util::GDataToDriveFileError(status) == DRIVE_FILE_OK, 168 about_resource.get() != NULL); 169 170 if (util::GDataToDriveFileError(status) == DRIVE_FILE_OK) { 171 DCHECK(about_resource); 172 last_known_remote_changestamp_ = about_resource->largest_change_id(); 173 } 174 175 resource_metadata_->GetLargestChangestamp( 176 base::Bind(&ChangeListLoader::CompareChangestampsAndLoadIfNeeded, 177 weak_ptr_factory_.GetWeakPtr(), 178 directory_fetch_info, 179 callback, 180 base::Passed(&about_resource))); 181} 182 183void ChangeListLoader::CompareChangestampsAndLoadIfNeeded( 184 const DirectoryFetchInfo& directory_fetch_info, 185 const FileOperationCallback& callback, 186 scoped_ptr<google_apis::AboutResource> about_resource, 187 int64 local_changestamp) { 188 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 189 DCHECK(!callback.is_null()); 190 DCHECK(refreshing_); 191 192 int64 remote_changestamp = 193 about_resource ? about_resource->largest_change_id() : 0; 194 if (remote_changestamp > 0 && local_changestamp >= remote_changestamp) { 195 if (local_changestamp > remote_changestamp) { 196 LOG(WARNING) << "Cached client feed is fresher than server, client = " 197 << local_changestamp 198 << ", server = " 199 << remote_changestamp; 200 } 201 202 // No changes detected, tell the client that the loading was successful. 203 OnChangeListLoadComplete(callback, DRIVE_FILE_OK); 204 return; 205 } 206 207 int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0; 208 if (start_changestamp == 0 && !about_resource.get()) { 209 // Full update needs AboutResource. If this is a full update, we should just 210 // give up. Note that to exit from the feed loading, we always have to flush 211 // the pending callback tasks via OnChangeListLoadComplete. 212 OnChangeListLoadComplete(callback, DRIVE_FILE_ERROR_FAILED); 213 return; 214 } 215 216 if (directory_fetch_info.empty()) { 217 // If the caller is not interested in a particular directory, just start 218 // loading the change list. 219 LoadChangeListFromServer(about_resource.Pass(), 220 start_changestamp, 221 callback); 222 } else if (directory_fetch_info.changestamp() < remote_changestamp) { 223 // If the caller is interested in a particular directory, and the 224 // directory changestamp is older than server's, start loading the 225 // directory first. 226 DVLOG(1) << "Fast-fetching directory: " << directory_fetch_info.ToString() 227 << "; remote_changestamp: " << remote_changestamp; 228 const DirectoryFetchInfo new_directory_fetch_info( 229 directory_fetch_info.resource_id(), remote_changestamp); 230 DoLoadDirectoryFromServer( 231 new_directory_fetch_info, 232 base::Bind(&ChangeListLoader::StartLoadChangeListFromServer, 233 weak_ptr_factory_.GetWeakPtr(), 234 directory_fetch_info, 235 base::Passed(&about_resource), 236 start_changestamp, 237 callback)); 238 } else { 239 // The directory is up-to-date, but not the case for other parts. 240 // Proceed to change list loading. StartLoadChangeListFromServer will 241 // run |callback| for notifying the directory is ready before feed load. 242 StartLoadChangeListFromServer(directory_fetch_info, 243 about_resource.Pass(), 244 start_changestamp, 245 callback, 246 DRIVE_FILE_OK); 247 } 248} 249 250void ChangeListLoader::LoadChangeListFromServer( 251 scoped_ptr<google_apis::AboutResource> about_resource, 252 int64 start_changestamp, 253 const FileOperationCallback& callback) { 254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 255 DCHECK(!callback.is_null()); 256 DCHECK(refreshing_); 257 258 scoped_ptr<LoadFeedParams> load_params(new LoadFeedParams); 259 load_params->start_changestamp = start_changestamp; 260 LoadFromServer( 261 load_params.Pass(), 262 base::Bind(&ChangeListLoader::UpdateMetadataFromFeedAfterLoadFromServer, 263 weak_ptr_factory_.GetWeakPtr(), 264 base::Passed(&about_resource), 265 start_changestamp != 0, // is_delta_feed 266 callback)); 267} 268 269void ChangeListLoader::StartLoadChangeListFromServer( 270 const DirectoryFetchInfo& directory_fetch_info, 271 scoped_ptr<google_apis::AboutResource> about_resource, 272 int64 start_changestamp, 273 const FileOperationCallback& callback, 274 DriveFileError error) { 275 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 276 DCHECK(!callback.is_null()); 277 DCHECK(refreshing_); 278 279 if (error == DRIVE_FILE_OK) { 280 OnDirectoryLoadComplete(directory_fetch_info, callback, DRIVE_FILE_OK); 281 DVLOG(1) << "Fast-fetch was successful: " << directory_fetch_info.ToString() 282 << "; Start loading the change list"; 283 // Stop passing |callback| as it's just consumed. 284 LoadChangeListFromServer( 285 about_resource.Pass(), 286 start_changestamp, 287 base::Bind(&util::EmptyFileOperationCallback)); 288 } else { 289 // The directory fast-fetch failed, but the change list loading may 290 // succeed. Keep passing |callback| so it's run after the change list 291 // loading is complete. 292 LoadChangeListFromServer( 293 about_resource.Pass(), start_changestamp, callback); 294 } 295} 296 297void ChangeListLoader::OnGetAppList(google_apis::GDataErrorCode status, 298 scoped_ptr<google_apis::AppList> app_list) { 299 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 300 301 DriveFileError error = util::GDataToDriveFileError(status); 302 if (error != DRIVE_FILE_OK) 303 return; 304 305 if (app_list.get()) { 306 webapps_registry_->UpdateFromAppList(*app_list); 307 } 308} 309 310void ChangeListLoader::LoadFromServer(scoped_ptr<LoadFeedParams> params, 311 const LoadFeedListCallback& callback) { 312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 313 DCHECK(!callback.is_null()); 314 315 const base::TimeTicks start_time = base::TimeTicks::Now(); 316 317 // base::Passed() may get evaluated first, so get a pointer to params. 318 LoadFeedParams* params_ptr = params.get(); 319 scheduler_->GetResourceList( 320 params_ptr->feed_to_load, 321 params_ptr->start_changestamp, 322 params_ptr->search_query, 323 params_ptr->shared_with_me, 324 params_ptr->directory_resource_id, 325 base::Bind(&ChangeListLoader::LoadFromServerAfterGetResourceList, 326 weak_ptr_factory_.GetWeakPtr(), 327 base::Passed(¶ms), 328 callback, 329 start_time)); 330} 331 332void ChangeListLoader::LoadDirectoryFromServer( 333 const std::string& directory_resource_id, 334 const FileOperationCallback& callback) { 335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 336 DCHECK(!callback.is_null()); 337 338 // First fetch the latest changestamp to see if this directory needs to be 339 // updated. 340 scheduler_->GetAboutResource( 341 base::Bind( 342 &ChangeListLoader::LoadDirectoryFromServerAfterGetAbout, 343 weak_ptr_factory_.GetWeakPtr(), 344 directory_resource_id, 345 callback)); 346} 347 348void ChangeListLoader::LoadDirectoryFromServerAfterGetAbout( 349 const std::string& directory_resource_id, 350 const FileOperationCallback& callback, 351 google_apis::GDataErrorCode status, 352 scoped_ptr<google_apis::AboutResource> about_resource) { 353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 354 DCHECK(!callback.is_null()); 355 356 int64 remote_changestamp = 0; 357 if (util::GDataToDriveFileError(status) == DRIVE_FILE_OK) { 358 DCHECK(about_resource); 359 remote_changestamp = about_resource->largest_change_id(); 360 } 361 362 const DirectoryFetchInfo directory_fetch_info(directory_resource_id, 363 remote_changestamp); 364 DoLoadDirectoryFromServer(directory_fetch_info, callback); 365} 366 367void ChangeListLoader::DoLoadDirectoryFromServer( 368 const DirectoryFetchInfo& directory_fetch_info, 369 const FileOperationCallback& callback) { 370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 371 DCHECK(!callback.is_null()); 372 DCHECK(!directory_fetch_info.empty()); 373 DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString(); 374 375 scoped_ptr<LoadFeedParams> params(new LoadFeedParams); 376 params->directory_resource_id = directory_fetch_info.resource_id(); 377 LoadFromServer( 378 params.Pass(), 379 base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterLoad, 380 weak_ptr_factory_.GetWeakPtr(), 381 directory_fetch_info, 382 callback)); 383} 384 385void ChangeListLoader::DoLoadDirectoryFromServerAfterLoad( 386 const DirectoryFetchInfo& directory_fetch_info, 387 const FileOperationCallback& callback, 388 const ScopedVector<google_apis::ResourceList>& resource_list, 389 DriveFileError error) { 390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 391 DCHECK(!callback.is_null()); 392 DCHECK(!directory_fetch_info.empty()); 393 394 if (error != DRIVE_FILE_OK) { 395 LOG(ERROR) << "Failed to load directory: " 396 << directory_fetch_info.resource_id() 397 << ": " << error; 398 callback.Run(error); 399 return; 400 } 401 402 // Do not use |change_list_processor_| as it may be in use for other 403 // purposes. 404 ChangeListProcessor change_list_processor(resource_metadata_); 405 change_list_processor.FeedToEntryProtoMap(resource_list, NULL, NULL); 406 resource_metadata_->RefreshDirectory( 407 directory_fetch_info, 408 change_list_processor.entry_proto_map(), 409 base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh, 410 weak_ptr_factory_.GetWeakPtr(), 411 directory_fetch_info, 412 callback)); 413} 414 415void ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh( 416 const DirectoryFetchInfo& directory_fetch_info, 417 const FileOperationCallback& callback, 418 DriveFileError error, 419 const base::FilePath& directory_path) { 420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 421 DCHECK(!callback.is_null()); 422 423 DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString(); 424 callback.Run(error); 425 // Also notify the observers. 426 if (error == DRIVE_FILE_OK) { 427 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, 428 OnDirectoryChanged(directory_path)); 429 } 430} 431 432void ChangeListLoader::SearchFromServer( 433 const std::string& search_query, 434 bool shared_with_me, 435 const GURL& next_feed, 436 const LoadFeedListCallback& feed_load_callback) { 437 DCHECK(!feed_load_callback.is_null()); 438 439 scoped_ptr<LoadFeedParams> params(new LoadFeedParams); 440 params->search_query = search_query; 441 params->shared_with_me = shared_with_me; 442 params->feed_to_load = next_feed; 443 params->load_subsequent_feeds = false; 444 LoadFromServer(params.Pass(), feed_load_callback); 445} 446 447void ChangeListLoader::UpdateMetadataFromFeedAfterLoadFromServer( 448 scoped_ptr<google_apis::AboutResource> about_resource, 449 bool is_delta_feed, 450 const FileOperationCallback& callback, 451 const ScopedVector<google_apis::ResourceList>& feed_list, 452 DriveFileError error) { 453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 454 DCHECK(!callback.is_null()); 455 DCHECK(refreshing_); 456 457 if (error != DRIVE_FILE_OK) { 458 OnChangeListLoadComplete(callback, error); 459 return; 460 } 461 462 UpdateFromFeed(about_resource.Pass(), 463 feed_list, 464 is_delta_feed, 465 base::Bind(&ChangeListLoader::OnUpdateFromFeed, 466 weak_ptr_factory_.GetWeakPtr(), 467 !loaded(), // is_initial_load 468 callback)); 469} 470 471void ChangeListLoader::LoadFromServerAfterGetResourceList( 472 scoped_ptr<LoadFeedParams> params, 473 const LoadFeedListCallback& callback, 474 base::TimeTicks start_time, 475 google_apis::GDataErrorCode status, 476 scoped_ptr<google_apis::ResourceList> resource_list) { 477 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 478 DCHECK(!callback.is_null()); 479 480 if (params->feed_list.empty()) { 481 UMA_HISTOGRAM_TIMES("Drive.InitialFeedLoadTime", 482 base::TimeTicks::Now() - start_time); 483 } 484 485 DriveFileError error = util::GDataToDriveFileError(status); 486 if (error != DRIVE_FILE_OK) { 487 callback.Run(params->feed_list, error); 488 return; 489 } 490 DCHECK(resource_list); 491 492 GURL next_feed_url; 493 const bool has_next_feed_url = 494 params->load_subsequent_feeds && 495 resource_list->GetNextFeedURL(&next_feed_url); 496 497 // Add the current feed to the list of collected feeds for this directory. 498 params->feed_list.push_back(resource_list.release()); 499 500 // Compute and notify the number of entries fetched so far. 501 int num_accumulated_entries = 0; 502 for (size_t i = 0; i < params->feed_list.size(); ++i) 503 num_accumulated_entries += params->feed_list[i]->entries().size(); 504 505 // Check if we need to collect more data to complete the directory list. 506 if (has_next_feed_url && !next_feed_url.is_empty()) { 507 // Post an UI update event to make the UI smoother. 508 GetResourceListUiState* ui_state = params->ui_state.get(); 509 if (ui_state == NULL) { 510 ui_state = new GetResourceListUiState(base::TimeTicks::Now()); 511 params->ui_state.reset(ui_state); 512 } 513 DCHECK(ui_state); 514 515 if ((ui_state->num_fetched_documents - ui_state->num_showing_documents) 516 < kFetchUiUpdateStep) { 517 // Currently the UI update is stopped. Start UI periodic callback. 518 base::MessageLoopProxy::current()->PostTask( 519 FROM_HERE, 520 base::Bind(&ChangeListLoader::OnNotifyResourceListFetched, 521 weak_ptr_factory_.GetWeakPtr(), 522 ui_state->weak_ptr_factory.GetWeakPtr())); 523 } 524 ui_state->num_fetched_documents = num_accumulated_entries; 525 ui_state->feed_fetching_elapsed_time = base::TimeTicks::Now() - start_time; 526 527 // |params| will be passed to the callback and thus nulled. Extract the 528 // pointer so we can use it bellow. 529 LoadFeedParams* params_ptr = params.get(); 530 // Kick off the remaining part of the feeds. 531 scheduler_->GetResourceList( 532 next_feed_url, 533 params_ptr->start_changestamp, 534 params_ptr->search_query, 535 params_ptr->shared_with_me, 536 params_ptr->directory_resource_id, 537 base::Bind(&ChangeListLoader::LoadFromServerAfterGetResourceList, 538 weak_ptr_factory_.GetWeakPtr(), 539 base::Passed(¶ms), 540 callback, 541 start_time)); 542 return; 543 } 544 545 // Notify the observers that all document feeds are fetched. 546 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, 547 OnResourceListFetched(num_accumulated_entries)); 548 549 UMA_HISTOGRAM_TIMES("Drive.EntireFeedLoadTime", 550 base::TimeTicks::Now() - start_time); 551 552 // Run the callback so the client can process the retrieved feeds. 553 callback.Run(params->feed_list, DRIVE_FILE_OK); 554} 555 556void ChangeListLoader::OnNotifyResourceListFetched( 557 base::WeakPtr<GetResourceListUiState> ui_state) { 558 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 559 560 if (!ui_state) { 561 // The ui state instance is already released, which means the fetching 562 // is done and we don't need to update any more. 563 return; 564 } 565 566 base::TimeDelta ui_elapsed_time = 567 base::TimeTicks::Now() - ui_state->start_time; 568 569 if (ui_state->num_showing_documents + kFetchUiUpdateStep <= 570 ui_state->num_fetched_documents) { 571 ui_state->num_showing_documents += kFetchUiUpdateStep; 572 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, 573 OnResourceListFetched(ui_state->num_showing_documents)); 574 575 int num_remaining_ui_updates = 576 (ui_state->num_fetched_documents - ui_state->num_showing_documents) 577 / kFetchUiUpdateStep; 578 if (num_remaining_ui_updates > 0) { 579 // Heuristically, we use fetched time duration to calculate the next 580 // UI update timing. 581 base::TimeDelta remaining_duration = 582 ui_state->feed_fetching_elapsed_time - ui_elapsed_time; 583 base::TimeDelta interval = remaining_duration / num_remaining_ui_updates; 584 // If UI update is slow for some reason, the interval can be 585 // negative, or very small. This rarely happens but should be handled. 586 const int kMinIntervalMs = 10; 587 if (interval.InMilliseconds() < kMinIntervalMs) 588 interval = base::TimeDelta::FromMilliseconds(kMinIntervalMs); 589 590 base::MessageLoopProxy::current()->PostDelayedTask( 591 FROM_HERE, 592 base::Bind(&ChangeListLoader::OnNotifyResourceListFetched, 593 weak_ptr_factory_.GetWeakPtr(), 594 ui_state->weak_ptr_factory.GetWeakPtr()), 595 interval); 596 } 597 } 598} 599 600void ChangeListLoader::LoadIfNeeded( 601 const DirectoryFetchInfo& directory_fetch_info, 602 const FileOperationCallback& callback) { 603 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 604 DCHECK(!callback.is_null()); 605 606 // If feed has already been loaded, for normal feed fetch (= empty 607 // directory_fetch_info), we have nothing to do. For "fast fetch", we need to 608 // schedule a fetching if a feed refresh is currently running, because we 609 // don't want to wait a possibly large delta feed to arrive. 610 if (loaded() && (directory_fetch_info.empty() || !refreshing())) { 611 base::MessageLoopProxy::current()->PostTask( 612 FROM_HERE, 613 base::Bind(callback, DRIVE_FILE_OK)); 614 return; 615 } 616 617 // At this point, it is either !loaded() or refreshing(). 618 // If the change list loading is in progress, schedule the callback to 619 // run when it's ready (i.e. when the entire resource list is loaded, or 620 // the directory contents are available per "fast fetch"). 621 if (refreshing()) { 622 ScheduleRun(directory_fetch_info, callback); 623 return; 624 } 625 626 if (!directory_fetch_info.empty()) { 627 // Add a dummy task to so ScheduleRun() can check that the directory is 628 // being fetched. This will be cleared either in 629 // ProcessPendingLoadCallbackForDirectory() or FlushPendingLoadCallback(). 630 pending_load_callback_[directory_fetch_info.resource_id()].push_back( 631 base::Bind(&util::EmptyFileOperationCallback)); 632 } 633 634 // First start loading from the cache. 635 LoadFromCache(base::Bind(&ChangeListLoader::LoadAfterLoadFromCache, 636 weak_ptr_factory_.GetWeakPtr(), 637 directory_fetch_info, 638 callback)); 639} 640 641void ChangeListLoader::LoadAfterLoadFromCache( 642 const DirectoryFetchInfo& directory_fetch_info, 643 const FileOperationCallback& callback, 644 DriveFileError error) { 645 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 646 DCHECK(!callback.is_null()); 647 648 if (error == DRIVE_FILE_OK) { 649 loaded_ = true; 650 651 // The loading from the cache file succeeded. Change the refreshing state 652 // and tell the callback that the loading was successful. 653 OnChangeListLoadComplete(callback, DRIVE_FILE_OK); 654 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 655 observers_, 656 OnInitialFeedLoaded()); 657 658 // Load from server if needed (i.e. the cache is old). Note that we 659 // should still propagate |directory_fetch_info| though the directory is 660 // loaded first. This way, the UI can get notified via a directory change 661 // event as soon as the current directory contents are fetched. 662 LoadFromServerIfNeeded(directory_fetch_info, 663 base::Bind(&util::EmptyFileOperationCallback)); 664 } else { 665 // The loading from the cache file failed. Start loading from the 666 // server. Though the function name ends with "IfNeeded", this function 667 // should always start loading as the local changestamp is zero now. 668 LoadFromServerIfNeeded(directory_fetch_info, callback); 669 } 670} 671 672void ChangeListLoader::LoadFromCache(const FileOperationCallback& callback) { 673 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 674 DCHECK(!callback.is_null()); 675 DCHECK(!loaded_); 676 677 // Sets the refreshing flag, so that the caller does not send refresh requests 678 // in parallel (see DriveFileSystem::LoadFeedIfNeeded). 679 // 680 // The flag will be unset when loading from the cache is complete, or 681 // loading from the server is complete. 682 refreshing_ = true; 683 684 resource_metadata_->Load(callback); 685} 686 687void ChangeListLoader::SaveFileSystem() { 688 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 689 690 resource_metadata_->MaybeSave(); 691} 692 693void ChangeListLoader::UpdateFromFeed( 694 scoped_ptr<google_apis::AboutResource> about_resource, 695 const ScopedVector<google_apis::ResourceList>& feed_list, 696 bool is_delta_feed, 697 const base::Closure& update_finished_callback) { 698 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 699 DCHECK(!update_finished_callback.is_null()); 700 DVLOG(1) << "Updating directory with a feed"; 701 702 change_list_processor_.reset(new ChangeListProcessor(resource_metadata_)); 703 // Don't send directory content change notification while performing 704 // the initial content retrieval. 705 const bool should_notify_changed_directories = is_delta_feed; 706 707 change_list_processor_->ApplyFeeds( 708 about_resource.Pass(), 709 feed_list, 710 is_delta_feed, 711 base::Bind(&ChangeListLoader::NotifyDirectoryChangedAfterApplyFeed, 712 weak_ptr_factory_.GetWeakPtr(), 713 should_notify_changed_directories, 714 update_finished_callback)); 715} 716 717void ChangeListLoader::ScheduleRun( 718 const DirectoryFetchInfo& directory_fetch_info, 719 const FileOperationCallback& callback) { 720 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 721 DCHECK(!callback.is_null()); 722 DCHECK(refreshing_); 723 724 if (directory_fetch_info.empty()) { 725 // If the caller is not interested in a particular directory, just add the 726 // callback to the pending list and return. 727 pending_load_callback_[""].push_back(callback); 728 return; 729 } 730 731 const std::string& resource_id = directory_fetch_info.resource_id(); 732 733 // If the directory of interest is already scheduled to be fetched, add the 734 // callback to the pending list and return. 735 LoadCallbackMap::iterator it = pending_load_callback_.find(resource_id); 736 if (it != pending_load_callback_.end()) { 737 it->second.push_back(callback); 738 return; 739 } 740 741 // If the directory's changestamp is up-to-date, just schedule to run the 742 // callback, as there is no need to fetch the directory. 743 if (directory_fetch_info.changestamp() >= last_known_remote_changestamp_) { 744 base::MessageLoopProxy::current()->PostTask( 745 FROM_HERE, 746 base::Bind(callback, DRIVE_FILE_OK)); 747 return; 748 } 749 750 // The directory should be fetched. Add a dummy task to so ScheduleRun() 751 // can check that the directory is being fetched. 752 pending_load_callback_[resource_id].push_back( 753 base::Bind(&util::EmptyFileOperationCallback)); 754 DoLoadDirectoryFromServer( 755 directory_fetch_info, 756 base::Bind(&ChangeListLoader::OnDirectoryLoadComplete, 757 weak_ptr_factory_.GetWeakPtr(), 758 directory_fetch_info, 759 callback)); 760} 761 762void ChangeListLoader::NotifyDirectoryChangedAfterApplyFeed( 763 bool should_notify_changed_directories, 764 const base::Closure& update_finished_callback) { 765 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 766 DCHECK(change_list_processor_.get()); 767 DCHECK(!update_finished_callback.is_null()); 768 769 loaded_ = true; 770 771 if (should_notify_changed_directories) { 772 for (std::set<base::FilePath>::iterator dir_iter = 773 change_list_processor_->changed_dirs().begin(); 774 dir_iter != change_list_processor_->changed_dirs().end(); 775 ++dir_iter) { 776 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, 777 OnDirectoryChanged(*dir_iter)); 778 } 779 } 780 781 update_finished_callback.Run(); 782 783 // Cannot delete change_list_processor_ yet because we are in 784 // on_complete_callback_, which is owned by change_list_processor_. 785} 786 787void ChangeListLoader::OnUpdateFromFeed( 788 bool is_inital_load, 789 const FileOperationCallback& callback) { 790 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 791 DCHECK(!callback.is_null()); 792 793 OnChangeListLoadComplete(callback, DRIVE_FILE_OK); 794 if (is_inital_load) { 795 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 796 observers_, 797 OnInitialFeedLoaded()); 798 } 799 800 // Save file system metadata to disk. 801 SaveFileSystem(); 802 803 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 804 observers_, 805 OnFeedFromServerLoaded()); 806} 807 808void ChangeListLoader::OnChangeListLoadComplete( 809 const FileOperationCallback& callback, 810 DriveFileError error) { 811 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 812 DCHECK(!callback.is_null()); 813 814 refreshing_ = false; 815 callback.Run(error); 816 FlushPendingLoadCallback(error); 817} 818 819void ChangeListLoader::OnDirectoryLoadComplete( 820 const DirectoryFetchInfo& directory_fetch_info, 821 const FileOperationCallback& callback, 822 DriveFileError error) { 823 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 824 DCHECK(!callback.is_null()); 825 826 callback.Run(error); 827 ProcessPendingLoadCallbackForDirectory(directory_fetch_info.resource_id(), 828 error); 829} 830 831void ChangeListLoader::FlushPendingLoadCallback(DriveFileError error) { 832 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 833 DCHECK(!refreshing_); 834 835 for (LoadCallbackMap::iterator it = pending_load_callback_.begin(); 836 it != pending_load_callback_.end(); ++it) { 837 const std::vector<FileOperationCallback>& callbacks = it->second; 838 for (size_t i = 0; i < callbacks.size(); ++i) { 839 base::MessageLoopProxy::current()->PostTask( 840 FROM_HERE, 841 base::Bind(callbacks[i], error)); 842 } 843 } 844 pending_load_callback_.clear(); 845} 846 847void ChangeListLoader::ProcessPendingLoadCallbackForDirectory( 848 const std::string& resource_id, 849 DriveFileError error) { 850 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 851 852 LoadCallbackMap::iterator it = pending_load_callback_.find(resource_id); 853 if (it != pending_load_callback_.end()) { 854 DVLOG(1) << "Running callback for " << resource_id; 855 const std::vector<FileOperationCallback>& callbacks = it->second; 856 for (size_t i = 0; i < callbacks.size(); ++i) { 857 base::MessageLoopProxy::current()->PostTask( 858 FROM_HERE, 859 base::Bind(callbacks[i], error)); 860 } 861 pending_load_callback_.erase(it); 862 } 863} 864 865} // namespace drive 866