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/callback_helpers.h" 11 #include "base/metrics/histogram.h" 12 #include "base/strings/string_number_conversions.h" 13 #include "base/time/time.h" 14 #include "chrome/browser/chromeos/drive/change_list_loader_observer.h" 15 #include "chrome/browser/chromeos/drive/change_list_processor.h" 16 #include "chrome/browser/chromeos/drive/file_system_util.h" 17 #include "chrome/browser/chromeos/drive/job_scheduler.h" 18 #include "chrome/browser/chromeos/drive/resource_metadata.h" 19 #include "chrome/browser/drive/event_logger.h" 20 #include "content/public/browser/browser_thread.h" 21 #include "google_apis/drive/drive_api_parser.h" 22 #include "url/gurl.h" 23 24 using content::BrowserThread; 25 26 namespace drive { 27 namespace internal { 28 29 typedef base::Callback<void(FileError, ScopedVector<ChangeList>)> 30 FeedFetcherCallback; 31 32 class ChangeListLoader::FeedFetcher { 33 public: 34 virtual ~FeedFetcher() {} 35 virtual void Run(const FeedFetcherCallback& callback) = 0; 36 }; 37 38 namespace { 39 40 // Fetches all the (currently available) resource entries from the server. 41 class FullFeedFetcher : public ChangeListLoader::FeedFetcher { 42 public: 43 explicit FullFeedFetcher(JobScheduler* scheduler) 44 : scheduler_(scheduler), 45 weak_ptr_factory_(this) { 46 } 47 48 virtual ~FullFeedFetcher() { 49 } 50 51 virtual void Run(const FeedFetcherCallback& callback) OVERRIDE { 52 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 53 DCHECK(!callback.is_null()); 54 55 // Remember the time stamp for usage stats. 56 start_time_ = base::TimeTicks::Now(); 57 58 // This is full resource list fetch. 59 scheduler_->GetAllFileList( 60 base::Bind(&FullFeedFetcher::OnFileListFetched, 61 weak_ptr_factory_.GetWeakPtr(), callback)); 62 } 63 64 private: 65 void OnFileListFetched(const FeedFetcherCallback& callback, 66 google_apis::GDataErrorCode status, 67 scoped_ptr<google_apis::FileList> file_list) { 68 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 69 DCHECK(!callback.is_null()); 70 71 FileError error = GDataToFileError(status); 72 if (error != FILE_ERROR_OK) { 73 callback.Run(error, ScopedVector<ChangeList>()); 74 return; 75 } 76 77 DCHECK(file_list); 78 change_lists_.push_back(new ChangeList(*file_list)); 79 80 if (!file_list->next_link().is_empty()) { 81 // There is the remaining result so fetch it. 82 scheduler_->GetRemainingFileList( 83 file_list->next_link(), 84 base::Bind(&FullFeedFetcher::OnFileListFetched, 85 weak_ptr_factory_.GetWeakPtr(), callback)); 86 return; 87 } 88 89 UMA_HISTOGRAM_LONG_TIMES("Drive.FullFeedLoadTime", 90 base::TimeTicks::Now() - start_time_); 91 92 // Note: The fetcher is managed by ChangeListLoader, and the instance 93 // will be deleted in the callback. Do not touch the fields after this 94 // invocation. 95 callback.Run(FILE_ERROR_OK, change_lists_.Pass()); 96 } 97 98 JobScheduler* scheduler_; 99 ScopedVector<ChangeList> change_lists_; 100 base::TimeTicks start_time_; 101 base::WeakPtrFactory<FullFeedFetcher> weak_ptr_factory_; 102 DISALLOW_COPY_AND_ASSIGN(FullFeedFetcher); 103 }; 104 105 // Fetches the delta changes since |start_change_id|. 106 class DeltaFeedFetcher : public ChangeListLoader::FeedFetcher { 107 public: 108 DeltaFeedFetcher(JobScheduler* scheduler, int64 start_change_id) 109 : scheduler_(scheduler), 110 start_change_id_(start_change_id), 111 weak_ptr_factory_(this) { 112 } 113 114 virtual ~DeltaFeedFetcher() { 115 } 116 117 virtual void Run(const FeedFetcherCallback& callback) OVERRIDE { 118 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 119 DCHECK(!callback.is_null()); 120 121 scheduler_->GetChangeList( 122 start_change_id_, 123 base::Bind(&DeltaFeedFetcher::OnChangeListFetched, 124 weak_ptr_factory_.GetWeakPtr(), callback)); 125 } 126 127 private: 128 void OnChangeListFetched(const FeedFetcherCallback& callback, 129 google_apis::GDataErrorCode status, 130 scoped_ptr<google_apis::ChangeList> change_list) { 131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 132 DCHECK(!callback.is_null()); 133 134 FileError error = GDataToFileError(status); 135 if (error != FILE_ERROR_OK) { 136 callback.Run(error, ScopedVector<ChangeList>()); 137 return; 138 } 139 140 DCHECK(change_list); 141 change_lists_.push_back(new ChangeList(*change_list)); 142 143 if (!change_list->next_link().is_empty()) { 144 // There is the remaining result so fetch it. 145 scheduler_->GetRemainingChangeList( 146 change_list->next_link(), 147 base::Bind(&DeltaFeedFetcher::OnChangeListFetched, 148 weak_ptr_factory_.GetWeakPtr(), callback)); 149 return; 150 } 151 152 // Note: The fetcher is managed by ChangeListLoader, and the instance 153 // will be deleted in the callback. Do not touch the fields after this 154 // invocation. 155 callback.Run(FILE_ERROR_OK, change_lists_.Pass()); 156 } 157 158 JobScheduler* scheduler_; 159 int64 start_change_id_; 160 ScopedVector<ChangeList> change_lists_; 161 base::WeakPtrFactory<DeltaFeedFetcher> weak_ptr_factory_; 162 DISALLOW_COPY_AND_ASSIGN(DeltaFeedFetcher); 163 }; 164 165 } // namespace 166 167 LoaderController::LoaderController() 168 : lock_count_(0), 169 weak_ptr_factory_(this) { 170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 171 } 172 173 LoaderController::~LoaderController() { 174 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 175 } 176 177 scoped_ptr<base::ScopedClosureRunner> LoaderController::GetLock() { 178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 179 180 ++lock_count_; 181 return make_scoped_ptr(new base::ScopedClosureRunner( 182 base::Bind(&LoaderController::Unlock, 183 weak_ptr_factory_.GetWeakPtr()))); 184 } 185 186 void LoaderController::ScheduleRun(const base::Closure& task) { 187 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 188 DCHECK(!task.is_null()); 189 190 if (lock_count_ > 0) { 191 pending_tasks_.push_back(task); 192 } else { 193 task.Run(); 194 } 195 } 196 197 void LoaderController::Unlock() { 198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 199 DCHECK_LT(0, lock_count_); 200 201 if (--lock_count_ > 0) 202 return; 203 204 std::vector<base::Closure> tasks; 205 tasks.swap(pending_tasks_); 206 for (size_t i = 0; i < tasks.size(); ++i) 207 tasks[i].Run(); 208 } 209 210 AboutResourceLoader::AboutResourceLoader(JobScheduler* scheduler) 211 : scheduler_(scheduler), 212 current_update_task_id_(-1), 213 weak_ptr_factory_(this) { 214 } 215 216 AboutResourceLoader::~AboutResourceLoader() {} 217 218 void AboutResourceLoader::GetAboutResource( 219 const google_apis::AboutResourceCallback& callback) { 220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 221 DCHECK(!callback.is_null()); 222 223 // If the latest UpdateAboutResource task is still running. Wait for it, 224 if (pending_callbacks_.count(current_update_task_id_)) { 225 pending_callbacks_[current_update_task_id_].push_back(callback); 226 return; 227 } 228 229 if (cached_about_resource_) { 230 base::MessageLoopProxy::current()->PostTask( 231 FROM_HERE, 232 base::Bind( 233 callback, 234 google_apis::HTTP_NO_CONTENT, 235 base::Passed(scoped_ptr<google_apis::AboutResource>( 236 new google_apis::AboutResource(*cached_about_resource_))))); 237 } else { 238 UpdateAboutResource(callback); 239 } 240 } 241 242 void AboutResourceLoader::UpdateAboutResource( 243 const google_apis::AboutResourceCallback& callback) { 244 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 245 DCHECK(!callback.is_null()); 246 247 ++current_update_task_id_; 248 pending_callbacks_[current_update_task_id_].push_back(callback); 249 250 scheduler_->GetAboutResource( 251 base::Bind(&AboutResourceLoader::UpdateAboutResourceAfterGetAbout, 252 weak_ptr_factory_.GetWeakPtr(), 253 current_update_task_id_)); 254 } 255 256 void AboutResourceLoader::UpdateAboutResourceAfterGetAbout( 257 int task_id, 258 google_apis::GDataErrorCode status, 259 scoped_ptr<google_apis::AboutResource> about_resource) { 260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 261 FileError error = GDataToFileError(status); 262 263 const std::vector<google_apis::AboutResourceCallback> callbacks = 264 pending_callbacks_[task_id]; 265 pending_callbacks_.erase(task_id); 266 267 if (error != FILE_ERROR_OK) { 268 for (size_t i = 0; i < callbacks.size(); ++i) 269 callbacks[i].Run(status, scoped_ptr<google_apis::AboutResource>()); 270 return; 271 } 272 273 // Updates the cache when the resource is successfully obtained. 274 if (cached_about_resource_ && 275 cached_about_resource_->largest_change_id() > 276 about_resource->largest_change_id()) { 277 LOG(WARNING) << "Local cached about resource is fresher than server, " 278 << "local = " << cached_about_resource_->largest_change_id() 279 << ", server = " << about_resource->largest_change_id(); 280 } 281 cached_about_resource_.reset(new google_apis::AboutResource(*about_resource)); 282 283 for (size_t i = 0; i < callbacks.size(); ++i) { 284 callbacks[i].Run( 285 status, 286 make_scoped_ptr(new google_apis::AboutResource(*about_resource))); 287 } 288 } 289 290 ChangeListLoader::ChangeListLoader( 291 EventLogger* logger, 292 base::SequencedTaskRunner* blocking_task_runner, 293 ResourceMetadata* resource_metadata, 294 JobScheduler* scheduler, 295 AboutResourceLoader* about_resource_loader, 296 LoaderController* loader_controller) 297 : logger_(logger), 298 blocking_task_runner_(blocking_task_runner), 299 resource_metadata_(resource_metadata), 300 scheduler_(scheduler), 301 about_resource_loader_(about_resource_loader), 302 loader_controller_(loader_controller), 303 loaded_(false), 304 weak_ptr_factory_(this) { 305 } 306 307 ChangeListLoader::~ChangeListLoader() { 308 } 309 310 bool ChangeListLoader::IsRefreshing() const { 311 // Callback for change list loading is stored in pending_load_callback_. 312 // It is non-empty if and only if there is an in-flight loading operation. 313 return !pending_load_callback_.empty(); 314 } 315 316 void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) { 317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 318 observers_.AddObserver(observer); 319 } 320 321 void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) { 322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 323 observers_.RemoveObserver(observer); 324 } 325 326 void ChangeListLoader::CheckForUpdates(const FileOperationCallback& callback) { 327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 328 DCHECK(!callback.is_null()); 329 330 // We only start to check for updates iff the load is done. 331 // I.e., we ignore checking updates if not loaded to avoid starting the 332 // load without user's explicit interaction (such as opening Drive). 333 if (!loaded_ && !IsRefreshing()) 334 return; 335 336 // For each CheckForUpdates() request, always refresh the changestamp info. 337 about_resource_loader_->UpdateAboutResource( 338 base::Bind(&ChangeListLoader::OnAboutResourceUpdated, 339 weak_ptr_factory_.GetWeakPtr())); 340 341 if (IsRefreshing()) { 342 // There is in-flight loading. So keep the callback here, and check for 343 // updates when the in-flight loading is completed. 344 pending_update_check_callback_ = callback; 345 return; 346 } 347 348 DCHECK(loaded_); 349 logger_->Log(logging::LOG_INFO, "Checking for updates"); 350 Load(callback); 351 } 352 353 void ChangeListLoader::LoadIfNeeded(const FileOperationCallback& callback) { 354 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 355 DCHECK(!callback.is_null()); 356 357 // If the metadata is not yet loaded, start loading. 358 if (!loaded_ && !IsRefreshing()) 359 Load(callback); 360 } 361 362 void ChangeListLoader::Load(const FileOperationCallback& callback) { 363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 364 DCHECK(!callback.is_null()); 365 366 // Check if this is the first time this ChangeListLoader do loading. 367 // Note: IsRefreshing() depends on pending_load_callback_ so check in advance. 368 const bool is_initial_load = (!loaded_ && !IsRefreshing()); 369 370 // Register the callback function to be called when it is loaded. 371 pending_load_callback_.push_back(callback); 372 373 // If loading task is already running, do nothing. 374 if (pending_load_callback_.size() > 1) 375 return; 376 377 // Check the current status of local metadata, and start loading if needed. 378 int64* local_changestamp = new int64(0); 379 base::PostTaskAndReplyWithResult( 380 blocking_task_runner_.get(), 381 FROM_HERE, 382 base::Bind(&ResourceMetadata::GetLargestChangestamp, 383 base::Unretained(resource_metadata_), 384 local_changestamp), 385 base::Bind(&ChangeListLoader::LoadAfterGetLargestChangestamp, 386 weak_ptr_factory_.GetWeakPtr(), 387 is_initial_load, 388 base::Owned(local_changestamp))); 389 } 390 391 void ChangeListLoader::LoadAfterGetLargestChangestamp( 392 bool is_initial_load, 393 const int64* local_changestamp, 394 FileError error) { 395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 396 397 if (error != FILE_ERROR_OK) { 398 OnChangeListLoadComplete(error); 399 return; 400 } 401 402 if (is_initial_load && *local_changestamp > 0) { 403 // The local data is usable. Flush callbacks to tell loading was successful. 404 OnChangeListLoadComplete(FILE_ERROR_OK); 405 406 // Continues to load from server in background. 407 // Put dummy callbacks to indicate that fetching is still continuing. 408 pending_load_callback_.push_back( 409 base::Bind(&util::EmptyFileOperationCallback)); 410 } 411 412 about_resource_loader_->GetAboutResource( 413 base::Bind(&ChangeListLoader::LoadAfterGetAboutResource, 414 weak_ptr_factory_.GetWeakPtr(), 415 *local_changestamp)); 416 } 417 418 void ChangeListLoader::LoadAfterGetAboutResource( 419 int64 local_changestamp, 420 google_apis::GDataErrorCode status, 421 scoped_ptr<google_apis::AboutResource> about_resource) { 422 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 423 424 FileError error = GDataToFileError(status); 425 if (error != FILE_ERROR_OK) { 426 OnChangeListLoadComplete(error); 427 return; 428 } 429 430 DCHECK(about_resource); 431 432 int64 remote_changestamp = about_resource->largest_change_id(); 433 int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0; 434 if (local_changestamp >= remote_changestamp) { 435 if (local_changestamp > remote_changestamp) { 436 LOG(WARNING) << "Local resource metadata is fresher than server, " 437 << "local = " << local_changestamp 438 << ", server = " << remote_changestamp; 439 } 440 441 // No changes detected, tell the client that the loading was successful. 442 OnChangeListLoadComplete(FILE_ERROR_OK); 443 } else { 444 // Start loading the change list. 445 LoadChangeListFromServer(start_changestamp); 446 } 447 } 448 449 void ChangeListLoader::OnChangeListLoadComplete(FileError error) { 450 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 451 452 if (!loaded_ && error == FILE_ERROR_OK) { 453 loaded_ = true; 454 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 455 observers_, 456 OnInitialLoadComplete()); 457 } 458 459 for (size_t i = 0; i < pending_load_callback_.size(); ++i) { 460 base::MessageLoopProxy::current()->PostTask( 461 FROM_HERE, 462 base::Bind(pending_load_callback_[i], error)); 463 } 464 pending_load_callback_.clear(); 465 466 // If there is pending update check, try to load the change from the server 467 // again, because there may exist an update during the completed loading. 468 if (!pending_update_check_callback_.is_null()) { 469 Load(base::ResetAndReturn(&pending_update_check_callback_)); 470 } 471 } 472 473 void ChangeListLoader::OnAboutResourceUpdated( 474 google_apis::GDataErrorCode error, 475 scoped_ptr<google_apis::AboutResource> resource) { 476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 477 478 if (drive::GDataToFileError(error) != drive::FILE_ERROR_OK) { 479 logger_->Log(logging::LOG_ERROR, 480 "Failed to update the about resource: %s", 481 google_apis::GDataErrorCodeToString(error).c_str()); 482 return; 483 } 484 logger_->Log(logging::LOG_INFO, 485 "About resource updated to: %s", 486 base::Int64ToString(resource->largest_change_id()).c_str()); 487 } 488 489 void ChangeListLoader::LoadChangeListFromServer(int64 start_changestamp) { 490 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 491 DCHECK(!change_feed_fetcher_); 492 DCHECK(about_resource_loader_->cached_about_resource()); 493 494 bool is_delta_update = start_changestamp != 0; 495 496 // Set up feed fetcher. 497 if (is_delta_update) { 498 change_feed_fetcher_.reset( 499 new DeltaFeedFetcher(scheduler_, start_changestamp)); 500 } else { 501 change_feed_fetcher_.reset(new FullFeedFetcher(scheduler_)); 502 } 503 504 // Make a copy of cached_about_resource_ to remember at which changestamp we 505 // are fetching change list. 506 change_feed_fetcher_->Run( 507 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList, 508 weak_ptr_factory_.GetWeakPtr(), 509 base::Passed(make_scoped_ptr(new google_apis::AboutResource( 510 *about_resource_loader_->cached_about_resource()))), 511 is_delta_update)); 512 } 513 514 void ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList( 515 scoped_ptr<google_apis::AboutResource> about_resource, 516 bool is_delta_update, 517 FileError error, 518 ScopedVector<ChangeList> change_lists) { 519 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 520 DCHECK(about_resource); 521 522 // Delete the fetcher first. 523 change_feed_fetcher_.reset(); 524 525 if (error != FILE_ERROR_OK) { 526 OnChangeListLoadComplete(error); 527 return; 528 } 529 530 ChangeListProcessor* change_list_processor = 531 new ChangeListProcessor(resource_metadata_); 532 // Don't send directory content change notification while performing 533 // the initial content retrieval. 534 const bool should_notify_changed_directories = is_delta_update; 535 536 logger_->Log(logging::LOG_INFO, 537 "Apply change lists (is delta: %d)", 538 is_delta_update); 539 loader_controller_->ScheduleRun(base::Bind( 540 base::IgnoreResult( 541 &base::PostTaskAndReplyWithResult<FileError, FileError>), 542 blocking_task_runner_, 543 FROM_HERE, 544 base::Bind(&ChangeListProcessor::Apply, 545 base::Unretained(change_list_processor), 546 base::Passed(&about_resource), 547 base::Passed(&change_lists), 548 is_delta_update), 549 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterUpdate, 550 weak_ptr_factory_.GetWeakPtr(), 551 base::Owned(change_list_processor), 552 should_notify_changed_directories, 553 base::Time::Now()))); 554 } 555 556 void ChangeListLoader::LoadChangeListFromServerAfterUpdate( 557 ChangeListProcessor* change_list_processor, 558 bool should_notify_changed_directories, 559 const base::Time& start_time, 560 FileError error) { 561 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 562 563 const base::TimeDelta elapsed = base::Time::Now() - start_time; 564 logger_->Log(logging::LOG_INFO, 565 "Change lists applied (elapsed time: %sms)", 566 base::Int64ToString(elapsed.InMilliseconds()).c_str()); 567 568 if (should_notify_changed_directories) { 569 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 570 observers_, 571 OnFileChanged(change_list_processor->changed_files())); 572 } 573 574 OnChangeListLoadComplete(error); 575 576 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 577 observers_, 578 OnLoadFromServerComplete()); 579 } 580 581 } // namespace internal 582 } // namespace drive 583