directory_loader.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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/drive_api_util.h" 19#include "chrome/browser/drive/drive_service_interface.h" 20#include "chrome/browser/drive/event_logger.h" 21#include "content/public/browser/browser_thread.h" 22#include "google_apis/drive/drive_api_parser.h" 23#include "url/gurl.h" 24 25using content::BrowserThread; 26 27namespace drive { 28namespace internal { 29 30namespace { 31 32// Minimum changestamp gap required to start loading directory. 33const int kMinimumChangestampGap = 50; 34 35FileError CheckLocalState(ResourceMetadata* resource_metadata, 36 const google_apis::AboutResource& about_resource, 37 const std::string& local_id, 38 ResourceEntry* entry, 39 int64* local_changestamp) { 40 // Fill My Drive resource ID. 41 ResourceEntry mydrive; 42 FileError error = resource_metadata->GetResourceEntryByPath( 43 util::GetDriveMyDriveRootPath(), &mydrive); 44 if (error != FILE_ERROR_OK) 45 return error; 46 47 if (mydrive.resource_id().empty()) { 48 mydrive.set_resource_id(about_resource.root_folder_id()); 49 error = resource_metadata->RefreshEntry(mydrive); 50 if (error != FILE_ERROR_OK) 51 return error; 52 } 53 54 // Get entry. 55 error = resource_metadata->GetResourceEntryById(local_id, entry); 56 if (error != FILE_ERROR_OK) 57 return error; 58 59 // Get the local changestamp. 60 *local_changestamp = resource_metadata->GetLargestChangestamp(); 61 return FILE_ERROR_OK; 62} 63 64void ReadDirectoryAfterRead(const std::vector<ReadDirectoryCallback>& callbacks, 65 scoped_ptr<ResourceEntryVector> entries, 66 FileError error) { 67 if (error != FILE_ERROR_OK) 68 entries.reset(); 69 for (size_t i = 0; i < callbacks.size(); ++i) { 70 scoped_ptr<ResourceEntryVector> copied_entries; 71 if (entries) 72 copied_entries.reset(new ResourceEntryVector(*entries)); 73 74 callbacks[i].Run(error, copied_entries.Pass(), false /*has_more*/); 75 } 76} 77 78} // namespace 79 80// Fetches the resource entries in the directory with |directory_resource_id|. 81class DirectoryLoader::FeedFetcher { 82 public: 83 typedef base::Callback<void(FileError, ScopedVector<ChangeList>)> 84 FeedFetcherCallback; 85 86 FeedFetcher(JobScheduler* scheduler, 87 DriveServiceInterface* drive_service, 88 const std::string& directory_resource_id, 89 const std::string& root_folder_id) 90 : scheduler_(scheduler), 91 drive_service_(drive_service), 92 directory_resource_id_(directory_resource_id), 93 root_folder_id_(root_folder_id), 94 weak_ptr_factory_(this) { 95 } 96 97 ~FeedFetcher() { 98 } 99 100 void Run(const FeedFetcherCallback& callback) { 101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 102 DCHECK(!callback.is_null()); 103 DCHECK(!directory_resource_id_.empty()); 104 105 // Remember the time stamp for usage stats. 106 start_time_ = base::TimeTicks::Now(); 107 108 // We use WAPI's GetResourceListInDirectory even if Drive API v2 is 109 // enabled. This is the short term work around of the performance 110 // regression. 111 // TODO(hashimoto): Remove this. crbug.com/340931. 112 113 std::string resource_id = directory_resource_id_; 114 if (directory_resource_id_ == root_folder_id_) { 115 // GData WAPI doesn't accept the root directory id which is used in Drive 116 // API v2. So it is necessary to translate it here. 117 resource_id = util::kWapiRootDirectoryResourceId; 118 } 119 120 scheduler_->GetResourceListInDirectoryByWapi( 121 resource_id, 122 base::Bind(&FeedFetcher::OnResourceListFetched, 123 weak_ptr_factory_.GetWeakPtr(), callback)); 124 } 125 126 private: 127 void OnResourceListFetched( 128 const FeedFetcherCallback& callback, 129 google_apis::GDataErrorCode status, 130 scoped_ptr<google_apis::ResourceList> resource_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 // Add the current change list to the list of collected lists. 141 DCHECK(resource_list); 142 ChangeList* change_list = new ChangeList(*resource_list); 143 FixResourceIdInChangeList(change_list); 144 change_lists_.push_back(change_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_->GetRemainingResourceList( 150 next_url, 151 base::Bind(&FeedFetcher::OnResourceListFetched, 152 weak_ptr_factory_.GetWeakPtr(), callback)); 153 return; 154 } 155 156 UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime", 157 base::TimeTicks::Now() - start_time_); 158 159 // Note: The fetcher is managed by DirectoryLoader, and the instance 160 // will be deleted in the callback. Do not touch the fields after this 161 // invocation. 162 callback.Run(FILE_ERROR_OK, change_lists_.Pass()); 163 } 164 165 // Fixes resource IDs in |change_list| into the format that |drive_service_| 166 // can understand. Note that |change_list| contains IDs in GData WAPI format 167 // since currently we always use WAPI for fast fetch, regardless of the flag. 168 void FixResourceIdInChangeList(ChangeList* change_list) { 169 std::vector<ResourceEntry>* entries = change_list->mutable_entries(); 170 std::vector<std::string>* parent_resource_ids = 171 change_list->mutable_parent_resource_ids(); 172 for (size_t i = 0; i < entries->size(); ++i) { 173 ResourceEntry* entry = &(*entries)[i]; 174 if (entry->has_resource_id()) 175 entry->set_resource_id(FixResourceId(entry->resource_id())); 176 177 (*parent_resource_ids)[i] = FixResourceId((*parent_resource_ids)[i]); 178 } 179 } 180 181 std::string FixResourceId(const std::string& resource_id) { 182 if (resource_id == util::kWapiRootDirectoryResourceId) 183 return root_folder_id_; 184 return drive_service_->GetResourceIdCanonicalizer().Run(resource_id); 185 } 186 187 JobScheduler* scheduler_; 188 DriveServiceInterface* drive_service_; 189 std::string directory_resource_id_; 190 std::string root_folder_id_; 191 ScopedVector<ChangeList> change_lists_; 192 base::TimeTicks start_time_; 193 base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_; 194 DISALLOW_COPY_AND_ASSIGN(FeedFetcher); 195}; 196 197DirectoryLoader::DirectoryLoader( 198 EventLogger* logger, 199 base::SequencedTaskRunner* blocking_task_runner, 200 ResourceMetadata* resource_metadata, 201 JobScheduler* scheduler, 202 DriveServiceInterface* drive_service, 203 AboutResourceLoader* about_resource_loader, 204 LoaderController* loader_controller) 205 : logger_(logger), 206 blocking_task_runner_(blocking_task_runner), 207 resource_metadata_(resource_metadata), 208 scheduler_(scheduler), 209 drive_service_(drive_service), 210 about_resource_loader_(about_resource_loader), 211 loader_controller_(loader_controller), 212 weak_ptr_factory_(this) { 213} 214 215DirectoryLoader::~DirectoryLoader() { 216 STLDeleteElements(&fast_fetch_feed_fetcher_set_); 217} 218 219void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) { 220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 221 observers_.AddObserver(observer); 222} 223 224void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) { 225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 226 observers_.RemoveObserver(observer); 227} 228 229void DirectoryLoader::ReadDirectory(const base::FilePath& directory_path, 230 const ReadDirectoryCallback& callback) { 231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 232 DCHECK(!callback.is_null()); 233 234 ResourceEntry* entry = new ResourceEntry; 235 base::PostTaskAndReplyWithResult( 236 blocking_task_runner_.get(), 237 FROM_HERE, 238 base::Bind(&ResourceMetadata::GetResourceEntryByPath, 239 base::Unretained(resource_metadata_), 240 directory_path, 241 entry), 242 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry, 243 weak_ptr_factory_.GetWeakPtr(), 244 directory_path, 245 callback, 246 true, // should_try_loading_parent 247 base::Owned(entry))); 248} 249 250void DirectoryLoader::ReadDirectoryAfterGetEntry( 251 const base::FilePath& directory_path, 252 const ReadDirectoryCallback& callback, 253 bool should_try_loading_parent, 254 const ResourceEntry* entry, 255 FileError error) { 256 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 257 DCHECK(!callback.is_null()); 258 259 if (error == FILE_ERROR_NOT_FOUND && 260 should_try_loading_parent && 261 util::GetDriveGrandRootPath().IsParent(directory_path)) { 262 // This entry may be found after loading the parent. 263 ReadDirectory(directory_path.DirName(), 264 base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent, 265 weak_ptr_factory_.GetWeakPtr(), 266 directory_path, 267 callback)); 268 return; 269 } 270 if (error != FILE_ERROR_OK) { 271 callback.Run(error, scoped_ptr<ResourceEntryVector>(), false /*has_more*/); 272 return; 273 } 274 275 if (!entry->file_info().is_directory()) { 276 callback.Run(FILE_ERROR_NOT_A_DIRECTORY, 277 scoped_ptr<ResourceEntryVector>(), false /*has_more*/); 278 return; 279 } 280 281 DirectoryFetchInfo directory_fetch_info( 282 entry->local_id(), 283 entry->resource_id(), 284 entry->directory_specific_info().changestamp()); 285 286 // Register the callback function to be called when it is loaded. 287 const std::string& local_id = directory_fetch_info.local_id(); 288 pending_load_callback_[local_id].push_back(callback); 289 290 // If loading task for |local_id| is already running, do nothing. 291 if (pending_load_callback_[local_id].size() > 1) 292 return; 293 294 // Note: To be precise, we need to call UpdateAboutResource() here. However, 295 // - It is costly to do GetAboutResource HTTP request every time. 296 // - The chance using an old value is small; it only happens when 297 // ReadDirectory is called during one GetAboutResource roundtrip time 298 // of a change list fetching. 299 // - Even if the value is old, it just marks the directory as older. It may 300 // trigger one future unnecessary re-fetch, but it'll never lose data. 301 about_resource_loader_->GetAboutResource( 302 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource, 303 weak_ptr_factory_.GetWeakPtr(), local_id)); 304} 305 306void DirectoryLoader::ReadDirectoryAfterLoadParent( 307 const base::FilePath& directory_path, 308 const ReadDirectoryCallback& callback, 309 FileError error, 310 scoped_ptr<ResourceEntryVector> entries, 311 bool has_more) { 312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 313 DCHECK(!callback.is_null()); 314 315 if (has_more) 316 return; 317 318 if (error != FILE_ERROR_OK) { 319 callback.Run(error, scoped_ptr<ResourceEntryVector>(), false /*has_more*/); 320 return; 321 } 322 323 ResourceEntry* entry = new ResourceEntry; 324 base::PostTaskAndReplyWithResult( 325 blocking_task_runner_.get(), 326 FROM_HERE, 327 base::Bind(&ResourceMetadata::GetResourceEntryByPath, 328 base::Unretained(resource_metadata_), 329 directory_path, 330 entry), 331 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry, 332 weak_ptr_factory_.GetWeakPtr(), 333 directory_path, 334 callback, 335 false, // should_try_loading_parent 336 base::Owned(entry))); 337} 338 339void DirectoryLoader::ReadDirectoryAfterGetAboutResource( 340 const std::string& local_id, 341 google_apis::GDataErrorCode status, 342 scoped_ptr<google_apis::AboutResource> about_resource) { 343 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 344 345 FileError error = GDataToFileError(status); 346 if (error != FILE_ERROR_OK) { 347 OnDirectoryLoadComplete(local_id, error); 348 return; 349 } 350 351 DCHECK(about_resource); 352 353 // Check the current status of local metadata, and start loading if needed. 354 google_apis::AboutResource* about_resource_ptr = about_resource.get(); 355 ResourceEntry* entry = new ResourceEntry; 356 int64* local_changestamp = new int64; 357 base::PostTaskAndReplyWithResult( 358 blocking_task_runner_, 359 FROM_HERE, 360 base::Bind(&CheckLocalState, 361 resource_metadata_, 362 *about_resource_ptr, 363 local_id, 364 entry, 365 local_changestamp), 366 base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState, 367 weak_ptr_factory_.GetWeakPtr(), 368 base::Passed(&about_resource), 369 local_id, 370 base::Owned(entry), 371 base::Owned(local_changestamp))); 372} 373 374void DirectoryLoader::ReadDirectoryAfterCheckLocalState( 375 scoped_ptr<google_apis::AboutResource> about_resource, 376 const std::string& local_id, 377 const ResourceEntry* entry, 378 const int64* local_changestamp, 379 FileError error) { 380 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 381 DCHECK(about_resource); 382 383 if (error != FILE_ERROR_OK) { 384 OnDirectoryLoadComplete(local_id, error); 385 return; 386 } 387 // This entry does not exist on the server. 388 if (entry->resource_id().empty()) { 389 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK); 390 return; 391 } 392 393 int64 remote_changestamp = about_resource->largest_change_id(); 394 395 // Start loading the directory. 396 int64 directory_changestamp = std::max( 397 entry->directory_specific_info().changestamp(), *local_changestamp); 398 399 DirectoryFetchInfo directory_fetch_info( 400 local_id, entry->resource_id(), remote_changestamp); 401 402 // We may not fetch from the server at all if the local metadata is new 403 // enough, but we log this message here, so "Fast-fetch start" and 404 // "Fast-fetch complete" always match. 405 // TODO(satorux): Distinguish the "not fetching at all" case. 406 logger_->Log(logging::LOG_INFO, 407 "Fast-fetch start: %s; Server changestamp: %s", 408 directory_fetch_info.ToString().c_str(), 409 base::Int64ToString(remote_changestamp).c_str()); 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 logger_->Log(logging::LOG_INFO, 427 "Fast-fetch complete: %s => %s", 428 local_id.c_str(), 429 FileErrorToString(error).c_str()); 430 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id); 431 if (it != pending_load_callback_.end()) { 432 DVLOG(1) << "Running callback for " << local_id; 433 const std::vector<ReadDirectoryCallback>& callbacks = it->second; 434 ResourceEntryVector* entries = new ResourceEntryVector; 435 base::PostTaskAndReplyWithResult( 436 blocking_task_runner_.get(), 437 FROM_HERE, 438 base::Bind(&ResourceMetadata::ReadDirectoryById, 439 base::Unretained(resource_metadata_), local_id, entries), 440 base::Bind(&ReadDirectoryAfterRead, callbacks, 441 base::Passed(scoped_ptr<ResourceEntryVector>(entries)))); 442 pending_load_callback_.erase(it); 443 } 444} 445 446void DirectoryLoader::LoadDirectoryFromServer( 447 const DirectoryFetchInfo& directory_fetch_info) { 448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 449 DCHECK(!directory_fetch_info.empty()); 450 DCHECK(about_resource_loader_->cached_about_resource()); 451 DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString(); 452 453 FeedFetcher* fetcher = new FeedFetcher( 454 scheduler_, 455 drive_service_, 456 directory_fetch_info.resource_id(), 457 about_resource_loader_->cached_about_resource()->root_folder_id()); 458 fast_fetch_feed_fetcher_set_.insert(fetcher); 459 fetcher->Run( 460 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad, 461 weak_ptr_factory_.GetWeakPtr(), 462 directory_fetch_info, 463 fetcher)); 464} 465 466void DirectoryLoader::LoadDirectoryFromServerAfterLoad( 467 const DirectoryFetchInfo& directory_fetch_info, 468 FeedFetcher* fetcher, 469 FileError error, 470 ScopedVector<ChangeList> change_lists) { 471 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 472 DCHECK(!directory_fetch_info.empty()); 473 474 // Delete the fetcher. 475 fast_fetch_feed_fetcher_set_.erase(fetcher); 476 delete fetcher; 477 478 if (error != FILE_ERROR_OK) { 479 LOG(ERROR) << "Failed to load directory: " 480 << directory_fetch_info.local_id() 481 << ": " << FileErrorToString(error); 482 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error); 483 return; 484 } 485 486 base::FilePath* directory_path = new base::FilePath; 487 loader_controller_->ScheduleRun(base::Bind( 488 base::IgnoreResult( 489 &base::PostTaskAndReplyWithResult<FileError, FileError>), 490 blocking_task_runner_, 491 FROM_HERE, 492 base::Bind(&ChangeListProcessor::RefreshDirectory, 493 resource_metadata_, 494 directory_fetch_info, 495 base::Passed(&change_lists), 496 directory_path), 497 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterRefresh, 498 weak_ptr_factory_.GetWeakPtr(), 499 directory_fetch_info, 500 base::Owned(directory_path)))); 501} 502 503void DirectoryLoader::LoadDirectoryFromServerAfterRefresh( 504 const DirectoryFetchInfo& directory_fetch_info, 505 const base::FilePath* directory_path, 506 FileError error) { 507 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 508 509 DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString(); 510 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error); 511 512 // Also notify the observers. 513 if (error == FILE_ERROR_OK && !directory_path->empty()) { 514 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, 515 OnDirectoryChanged(*directory_path)); 516 } 517} 518 519} // namespace internal 520} // namespace drive 521