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