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 weak_ptr_factory_(this) { 213 } 214 215 AboutResourceLoader::~AboutResourceLoader() {} 216 217 void AboutResourceLoader::GetAboutResource( 218 const google_apis::AboutResourceCallback& callback) { 219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 220 DCHECK(!callback.is_null()); 221 222 if (cached_about_resource_) { 223 base::MessageLoopProxy::current()->PostTask( 224 FROM_HERE, 225 base::Bind( 226 callback, 227 google_apis::HTTP_NO_CONTENT, 228 base::Passed(scoped_ptr<google_apis::AboutResource>( 229 new google_apis::AboutResource(*cached_about_resource_))))); 230 } else { 231 UpdateAboutResource(callback); 232 } 233 } 234 235 void AboutResourceLoader::UpdateAboutResource( 236 const google_apis::AboutResourceCallback& callback) { 237 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 238 DCHECK(!callback.is_null()); 239 240 scheduler_->GetAboutResource( 241 base::Bind(&AboutResourceLoader::UpdateAboutResourceAfterGetAbout, 242 weak_ptr_factory_.GetWeakPtr(), 243 callback)); 244 } 245 246 void AboutResourceLoader::UpdateAboutResourceAfterGetAbout( 247 const google_apis::AboutResourceCallback& callback, 248 google_apis::GDataErrorCode status, 249 scoped_ptr<google_apis::AboutResource> about_resource) { 250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 251 DCHECK(!callback.is_null()); 252 FileError error = GDataToFileError(status); 253 254 if (error == FILE_ERROR_OK) { 255 if (cached_about_resource_ && 256 cached_about_resource_->largest_change_id() > 257 about_resource->largest_change_id()) { 258 LOG(WARNING) << "Local cached about resource is fresher than server, " 259 << "local = " << cached_about_resource_->largest_change_id() 260 << ", server = " << about_resource->largest_change_id(); 261 } 262 263 cached_about_resource_.reset( 264 new google_apis::AboutResource(*about_resource)); 265 } 266 267 callback.Run(status, about_resource.Pass()); 268 } 269 270 ChangeListLoader::ChangeListLoader( 271 EventLogger* logger, 272 base::SequencedTaskRunner* blocking_task_runner, 273 ResourceMetadata* resource_metadata, 274 JobScheduler* scheduler, 275 AboutResourceLoader* about_resource_loader, 276 LoaderController* loader_controller) 277 : logger_(logger), 278 blocking_task_runner_(blocking_task_runner), 279 resource_metadata_(resource_metadata), 280 scheduler_(scheduler), 281 about_resource_loader_(about_resource_loader), 282 loader_controller_(loader_controller), 283 loaded_(false), 284 weak_ptr_factory_(this) { 285 } 286 287 ChangeListLoader::~ChangeListLoader() { 288 } 289 290 bool ChangeListLoader::IsRefreshing() const { 291 // Callback for change list loading is stored in pending_load_callback_. 292 // It is non-empty if and only if there is an in-flight loading operation. 293 return !pending_load_callback_.empty(); 294 } 295 296 void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) { 297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 298 observers_.AddObserver(observer); 299 } 300 301 void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) { 302 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 303 observers_.RemoveObserver(observer); 304 } 305 306 void ChangeListLoader::CheckForUpdates(const FileOperationCallback& callback) { 307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 308 DCHECK(!callback.is_null()); 309 310 if (IsRefreshing()) { 311 // There is in-flight loading. So keep the callback here, and check for 312 // updates when the in-flight loading is completed. 313 pending_update_check_callback_ = callback; 314 return; 315 } 316 317 if (loaded_) { 318 // We only start to check for updates iff the load is done. 319 // I.e., we ignore checking updates if not loaded to avoid starting the 320 // load without user's explicit interaction (such as opening Drive). 321 logger_->Log(logging::LOG_INFO, "Checking for updates"); 322 Load(callback); 323 } 324 } 325 326 void ChangeListLoader::LoadIfNeeded(const FileOperationCallback& callback) { 327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 328 DCHECK(!callback.is_null()); 329 330 // If the metadata is not yet loaded, start loading. 331 if (!loaded_) 332 Load(callback); 333 } 334 335 void ChangeListLoader::Load(const FileOperationCallback& callback) { 336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 337 DCHECK(!callback.is_null()); 338 339 // Check if this is the first time this ChangeListLoader do loading. 340 // Note: IsRefreshing() depends on pending_load_callback_ so check in advance. 341 const bool is_initial_load = (!loaded_ && !IsRefreshing()); 342 343 // Register the callback function to be called when it is loaded. 344 pending_load_callback_.push_back(callback); 345 346 // If loading task is already running, do nothing. 347 if (pending_load_callback_.size() > 1) 348 return; 349 350 // Check the current status of local metadata, and start loading if needed. 351 int64* local_changestamp = new int64(0); 352 base::PostTaskAndReplyWithResult( 353 blocking_task_runner_, 354 FROM_HERE, 355 base::Bind(&ResourceMetadata::GetLargestChangestamp, 356 base::Unretained(resource_metadata_), 357 local_changestamp), 358 base::Bind(&ChangeListLoader::LoadAfterGetLargestChangestamp, 359 weak_ptr_factory_.GetWeakPtr(), 360 is_initial_load, 361 base::Owned(local_changestamp))); 362 } 363 364 void ChangeListLoader::LoadAfterGetLargestChangestamp( 365 bool is_initial_load, 366 const int64* local_changestamp, 367 FileError error) { 368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 369 370 if (error != FILE_ERROR_OK) { 371 OnChangeListLoadComplete(error); 372 return; 373 } 374 375 if (is_initial_load && *local_changestamp > 0) { 376 // The local data is usable. Flush callbacks to tell loading was successful. 377 OnChangeListLoadComplete(FILE_ERROR_OK); 378 379 // Continues to load from server in background. 380 // Put dummy callbacks to indicate that fetching is still continuing. 381 pending_load_callback_.push_back( 382 base::Bind(&util::EmptyFileOperationCallback)); 383 } 384 385 about_resource_loader_->UpdateAboutResource( 386 base::Bind(&ChangeListLoader::LoadAfterGetAboutResource, 387 weak_ptr_factory_.GetWeakPtr(), 388 *local_changestamp)); 389 } 390 391 void ChangeListLoader::LoadAfterGetAboutResource( 392 int64 local_changestamp, 393 google_apis::GDataErrorCode status, 394 scoped_ptr<google_apis::AboutResource> about_resource) { 395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 396 397 FileError error = GDataToFileError(status); 398 if (error != FILE_ERROR_OK) { 399 OnChangeListLoadComplete(error); 400 return; 401 } 402 403 DCHECK(about_resource); 404 405 int64 remote_changestamp = about_resource->largest_change_id(); 406 int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0; 407 if (local_changestamp >= remote_changestamp) { 408 if (local_changestamp > remote_changestamp) { 409 LOG(WARNING) << "Local resource metadata is fresher than server, " 410 << "local = " << local_changestamp 411 << ", server = " << remote_changestamp; 412 } 413 414 // No changes detected, tell the client that the loading was successful. 415 OnChangeListLoadComplete(FILE_ERROR_OK); 416 } else { 417 // Start loading the change list. 418 LoadChangeListFromServer(start_changestamp); 419 } 420 } 421 422 void ChangeListLoader::OnChangeListLoadComplete(FileError error) { 423 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 424 425 if (!loaded_ && error == FILE_ERROR_OK) { 426 loaded_ = true; 427 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 428 observers_, 429 OnInitialLoadComplete()); 430 } 431 432 for (size_t i = 0; i < pending_load_callback_.size(); ++i) { 433 base::MessageLoopProxy::current()->PostTask( 434 FROM_HERE, 435 base::Bind(pending_load_callback_[i], error)); 436 } 437 pending_load_callback_.clear(); 438 439 // If there is pending update check, try to load the change from the server 440 // again, because there may exist an update during the completed loading. 441 if (!pending_update_check_callback_.is_null()) { 442 Load(base::ResetAndReturn(&pending_update_check_callback_)); 443 } 444 } 445 446 void ChangeListLoader::LoadChangeListFromServer(int64 start_changestamp) { 447 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 448 DCHECK(!change_feed_fetcher_); 449 DCHECK(about_resource_loader_->cached_about_resource()); 450 451 bool is_delta_update = start_changestamp != 0; 452 453 // Set up feed fetcher. 454 if (is_delta_update) { 455 change_feed_fetcher_.reset( 456 new DeltaFeedFetcher(scheduler_, start_changestamp)); 457 } else { 458 change_feed_fetcher_.reset(new FullFeedFetcher(scheduler_)); 459 } 460 461 // Make a copy of cached_about_resource_ to remember at which changestamp we 462 // are fetching change list. 463 change_feed_fetcher_->Run( 464 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList, 465 weak_ptr_factory_.GetWeakPtr(), 466 base::Passed(make_scoped_ptr(new google_apis::AboutResource( 467 *about_resource_loader_->cached_about_resource()))), 468 is_delta_update)); 469 } 470 471 void ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList( 472 scoped_ptr<google_apis::AboutResource> about_resource, 473 bool is_delta_update, 474 FileError error, 475 ScopedVector<ChangeList> change_lists) { 476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 477 DCHECK(about_resource); 478 479 // Delete the fetcher first. 480 change_feed_fetcher_.reset(); 481 482 if (error != FILE_ERROR_OK) { 483 OnChangeListLoadComplete(error); 484 return; 485 } 486 487 ChangeListProcessor* change_list_processor = 488 new ChangeListProcessor(resource_metadata_); 489 // Don't send directory content change notification while performing 490 // the initial content retrieval. 491 const bool should_notify_changed_directories = is_delta_update; 492 493 logger_->Log(logging::LOG_INFO, 494 "Apply change lists (is delta: %d)", 495 is_delta_update); 496 loader_controller_->ScheduleRun(base::Bind( 497 base::IgnoreResult( 498 &base::PostTaskAndReplyWithResult<FileError, FileError>), 499 blocking_task_runner_, 500 FROM_HERE, 501 base::Bind(&ChangeListProcessor::Apply, 502 base::Unretained(change_list_processor), 503 base::Passed(&about_resource), 504 base::Passed(&change_lists), 505 is_delta_update), 506 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterUpdate, 507 weak_ptr_factory_.GetWeakPtr(), 508 base::Owned(change_list_processor), 509 should_notify_changed_directories, 510 base::Time::Now()))); 511 } 512 513 void ChangeListLoader::LoadChangeListFromServerAfterUpdate( 514 ChangeListProcessor* change_list_processor, 515 bool should_notify_changed_directories, 516 const base::Time& start_time, 517 FileError error) { 518 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 519 520 const base::TimeDelta elapsed = base::Time::Now() - start_time; 521 logger_->Log(logging::LOG_INFO, 522 "Change lists applied (elapsed time: %sms)", 523 base::Int64ToString(elapsed.InMilliseconds()).c_str()); 524 525 if (should_notify_changed_directories) { 526 for (std::set<base::FilePath>::iterator dir_iter = 527 change_list_processor->changed_dirs().begin(); 528 dir_iter != change_list_processor->changed_dirs().end(); 529 ++dir_iter) { 530 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, 531 OnDirectoryChanged(*dir_iter)); 532 } 533 } 534 535 OnChangeListLoadComplete(error); 536 537 FOR_EACH_OBSERVER(ChangeListLoaderObserver, 538 observers_, 539 OnLoadFromServerComplete()); 540 } 541 542 } // namespace internal 543 } // namespace drive 544