// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/drive/directory_loader.h" #include "base/callback.h" #include "base/callback_helpers.h" #include "base/metrics/histogram.h" #include "base/strings/string_number_conversions.h" #include "base/time/time.h" #include "chrome/browser/chromeos/drive/change_list_loader.h" #include "chrome/browser/chromeos/drive/change_list_loader_observer.h" #include "chrome/browser/chromeos/drive/change_list_processor.h" #include "components/drive/event_logger.h" #include "components/drive/file_system_core_util.h" #include "components/drive/job_scheduler.h" #include "components/drive/resource_metadata.h" #include "google_apis/drive/drive_api_parser.h" #include "url/gurl.h" namespace drive { namespace internal { namespace { // Minimum changestamp gap required to start loading directory. const int kMinimumChangestampGap = 50; FileError CheckLocalState(ResourceMetadata* resource_metadata, const google_apis::AboutResource& about_resource, const std::string& local_id, ResourceEntry* entry, int64* local_changestamp) { // Fill My Drive resource ID. ResourceEntry mydrive; FileError error = resource_metadata->GetResourceEntryByPath( util::GetDriveMyDriveRootPath(), &mydrive); if (error != FILE_ERROR_OK) return error; if (mydrive.resource_id().empty()) { mydrive.set_resource_id(about_resource.root_folder_id()); error = resource_metadata->RefreshEntry(mydrive); if (error != FILE_ERROR_OK) return error; } // Get entry. error = resource_metadata->GetResourceEntryById(local_id, entry); if (error != FILE_ERROR_OK) return error; // Get the local changestamp. return resource_metadata->GetLargestChangestamp(local_changestamp); } FileError UpdateChangestamp(ResourceMetadata* resource_metadata, const DirectoryFetchInfo& directory_fetch_info, base::FilePath* directory_path) { // Update the directory changestamp. ResourceEntry directory; FileError error = resource_metadata->GetResourceEntryById( directory_fetch_info.local_id(), &directory); if (error != FILE_ERROR_OK) return error; if (!directory.file_info().is_directory()) return FILE_ERROR_NOT_A_DIRECTORY; directory.mutable_directory_specific_info()->set_changestamp( directory_fetch_info.changestamp()); error = resource_metadata->RefreshEntry(directory); if (error != FILE_ERROR_OK) return error; // Get the directory path. return resource_metadata->GetFilePath(directory_fetch_info.local_id(), directory_path); } } // namespace struct DirectoryLoader::ReadDirectoryCallbackState { ReadDirectoryEntriesCallback entries_callback; FileOperationCallback completion_callback; std::set sent_entry_names; }; // Fetches the resource entries in the directory with |directory_resource_id|. class DirectoryLoader::FeedFetcher { public: FeedFetcher(DirectoryLoader* loader, const DirectoryFetchInfo& directory_fetch_info, const std::string& root_folder_id) : loader_(loader), directory_fetch_info_(directory_fetch_info), root_folder_id_(root_folder_id), weak_ptr_factory_(this) { } ~FeedFetcher() { } void Run(const FileOperationCallback& callback) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!callback.is_null()); DCHECK(!directory_fetch_info_.resource_id().empty()); // Remember the time stamp for usage stats. start_time_ = base::TimeTicks::Now(); loader_->scheduler_->GetFileListInDirectory( directory_fetch_info_.resource_id(), base::Bind(&FeedFetcher::OnFileListFetched, weak_ptr_factory_.GetWeakPtr(), callback)); } private: void OnFileListFetched(const FileOperationCallback& callback, google_apis::DriveApiErrorCode status, scoped_ptr file_list) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!callback.is_null()); FileError error = GDataToFileError(status); if (error != FILE_ERROR_OK) { callback.Run(error); return; } DCHECK(file_list); scoped_ptr change_list(new ChangeList(*file_list)); GURL next_url = file_list->next_link(); ResourceEntryVector* entries = new ResourceEntryVector; loader_->loader_controller_->ScheduleRun(base::Bind( base::IgnoreResult( &base::PostTaskAndReplyWithResult), loader_->blocking_task_runner_, FROM_HERE, base::Bind(&ChangeListProcessor::RefreshDirectory, loader_->resource_metadata_, directory_fetch_info_, base::Passed(&change_list), entries), base::Bind(&FeedFetcher::OnDirectoryRefreshed, weak_ptr_factory_.GetWeakPtr(), callback, next_url, base::Owned(entries)))); } void OnDirectoryRefreshed( const FileOperationCallback& callback, const GURL& next_url, const std::vector* refreshed_entries, FileError error) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!callback.is_null()); if (error != FILE_ERROR_OK) { callback.Run(error); return; } loader_->SendEntries(directory_fetch_info_.local_id(), *refreshed_entries); if (!next_url.is_empty()) { // There is the remaining result so fetch it. loader_->scheduler_->GetRemainingFileList( next_url, base::Bind(&FeedFetcher::OnFileListFetched, weak_ptr_factory_.GetWeakPtr(), callback)); return; } UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime", base::TimeTicks::Now() - start_time_); // Note: The fetcher is managed by DirectoryLoader, and the instance // will be deleted in the callback. Do not touch the fields after this // invocation. callback.Run(FILE_ERROR_OK); } DirectoryLoader* loader_; DirectoryFetchInfo directory_fetch_info_; std::string root_folder_id_; base::TimeTicks start_time_; base::ThreadChecker thread_checker_; base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(FeedFetcher); }; DirectoryLoader::DirectoryLoader( EventLogger* logger, base::SequencedTaskRunner* blocking_task_runner, ResourceMetadata* resource_metadata, JobScheduler* scheduler, AboutResourceLoader* about_resource_loader, LoaderController* loader_controller) : logger_(logger), blocking_task_runner_(blocking_task_runner), resource_metadata_(resource_metadata), scheduler_(scheduler), about_resource_loader_(about_resource_loader), loader_controller_(loader_controller), weak_ptr_factory_(this) { } DirectoryLoader::~DirectoryLoader() { STLDeleteElements(&fast_fetch_feed_fetcher_set_); } void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) { DCHECK(thread_checker_.CalledOnValidThread()); observers_.AddObserver(observer); } void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) { DCHECK(thread_checker_.CalledOnValidThread()); observers_.RemoveObserver(observer); } void DirectoryLoader::ReadDirectory( const base::FilePath& directory_path, const ReadDirectoryEntriesCallback& entries_callback, const FileOperationCallback& completion_callback) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!completion_callback.is_null()); ResourceEntry* entry = new ResourceEntry; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&ResourceMetadata::GetResourceEntryByPath, base::Unretained(resource_metadata_), directory_path, entry), base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry, weak_ptr_factory_.GetWeakPtr(), directory_path, entries_callback, completion_callback, true, // should_try_loading_parent base::Owned(entry))); } void DirectoryLoader::ReadDirectoryAfterGetEntry( const base::FilePath& directory_path, const ReadDirectoryEntriesCallback& entries_callback, const FileOperationCallback& completion_callback, bool should_try_loading_parent, const ResourceEntry* entry, FileError error) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!completion_callback.is_null()); if (error == FILE_ERROR_NOT_FOUND && should_try_loading_parent && util::GetDriveGrandRootPath().IsParent(directory_path)) { // This entry may be found after loading the parent. ReadDirectory(directory_path.DirName(), ReadDirectoryEntriesCallback(), base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent, weak_ptr_factory_.GetWeakPtr(), directory_path, entries_callback, completion_callback)); return; } if (error != FILE_ERROR_OK) { completion_callback.Run(error); return; } if (!entry->file_info().is_directory()) { completion_callback.Run(FILE_ERROR_NOT_A_DIRECTORY); return; } DirectoryFetchInfo directory_fetch_info( entry->local_id(), entry->resource_id(), entry->directory_specific_info().changestamp()); // Register the callback function to be called when it is loaded. const std::string& local_id = directory_fetch_info.local_id(); ReadDirectoryCallbackState callback_state; callback_state.entries_callback = entries_callback; callback_state.completion_callback = completion_callback; pending_load_callback_[local_id].push_back(callback_state); // If loading task for |local_id| is already running, do nothing. if (pending_load_callback_[local_id].size() > 1) return; about_resource_loader_->GetAboutResource( base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource, weak_ptr_factory_.GetWeakPtr(), local_id)); } void DirectoryLoader::ReadDirectoryAfterLoadParent( const base::FilePath& directory_path, const ReadDirectoryEntriesCallback& entries_callback, const FileOperationCallback& completion_callback, FileError error) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!completion_callback.is_null()); if (error != FILE_ERROR_OK) { completion_callback.Run(error); return; } ResourceEntry* entry = new ResourceEntry; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&ResourceMetadata::GetResourceEntryByPath, base::Unretained(resource_metadata_), directory_path, entry), base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry, weak_ptr_factory_.GetWeakPtr(), directory_path, entries_callback, completion_callback, false, // should_try_loading_parent base::Owned(entry))); } void DirectoryLoader::ReadDirectoryAfterGetAboutResource( const std::string& local_id, google_apis::DriveApiErrorCode status, scoped_ptr about_resource) { DCHECK(thread_checker_.CalledOnValidThread()); FileError error = GDataToFileError(status); if (error != FILE_ERROR_OK) { OnDirectoryLoadComplete(local_id, error); return; } DCHECK(about_resource); // Check the current status of local metadata, and start loading if needed. google_apis::AboutResource* about_resource_ptr = about_resource.get(); ResourceEntry* entry = new ResourceEntry; int64* local_changestamp = new int64; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&CheckLocalState, resource_metadata_, *about_resource_ptr, local_id, entry, local_changestamp), base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState, weak_ptr_factory_.GetWeakPtr(), base::Passed(&about_resource), local_id, base::Owned(entry), base::Owned(local_changestamp))); } void DirectoryLoader::ReadDirectoryAfterCheckLocalState( scoped_ptr about_resource, const std::string& local_id, const ResourceEntry* entry, const int64* local_changestamp, FileError error) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(about_resource); if (error != FILE_ERROR_OK) { OnDirectoryLoadComplete(local_id, error); return; } // This entry does not exist on the server. if (entry->resource_id().empty()) { OnDirectoryLoadComplete(local_id, FILE_ERROR_OK); return; } int64 remote_changestamp = about_resource->largest_change_id(); // Start loading the directory. int64 directory_changestamp = std::max( entry->directory_specific_info().changestamp(), *local_changestamp); DirectoryFetchInfo directory_fetch_info( local_id, entry->resource_id(), remote_changestamp); // If the directory's changestamp is up-to-date or the global changestamp of // the metadata DB is new enough (which means the normal changelist loading // should finish very soon), just schedule to run the callback, as there is no // need to fetch the directory. if (directory_changestamp >= remote_changestamp || *local_changestamp + kMinimumChangestampGap > remote_changestamp) { OnDirectoryLoadComplete(local_id, FILE_ERROR_OK); } else { // Start fetching the directory content, and mark it with the changestamp // |remote_changestamp|. LoadDirectoryFromServer(directory_fetch_info); } } void DirectoryLoader::OnDirectoryLoadComplete(const std::string& local_id, FileError error) { DCHECK(thread_checker_.CalledOnValidThread()); LoadCallbackMap::iterator it = pending_load_callback_.find(local_id); if (it == pending_load_callback_.end()) return; // No need to read metadata when no one needs entries. bool needs_to_send_entries = false; for (size_t i = 0; i < it->second.size(); ++i) { const ReadDirectoryCallbackState& callback_state = it->second[i]; if (!callback_state.entries_callback.is_null()) needs_to_send_entries = true; } if (!needs_to_send_entries) { OnDirectoryLoadCompleteAfterRead(local_id, NULL, FILE_ERROR_OK); return; } ResourceEntryVector* entries = new ResourceEntryVector; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&ResourceMetadata::ReadDirectoryById, base::Unretained(resource_metadata_), local_id, entries), base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead, weak_ptr_factory_.GetWeakPtr(), local_id, base::Owned(entries))); } void DirectoryLoader::OnDirectoryLoadCompleteAfterRead( const std::string& local_id, const ResourceEntryVector* entries, FileError error) { LoadCallbackMap::iterator it = pending_load_callback_.find(local_id); if (it != pending_load_callback_.end()) { DVLOG(1) << "Running callback for " << local_id; if (error == FILE_ERROR_OK && entries) SendEntries(local_id, *entries); for (size_t i = 0; i < it->second.size(); ++i) { const ReadDirectoryCallbackState& callback_state = it->second[i]; callback_state.completion_callback.Run(error); } pending_load_callback_.erase(it); } } void DirectoryLoader::SendEntries(const std::string& local_id, const ResourceEntryVector& entries) { LoadCallbackMap::iterator it = pending_load_callback_.find(local_id); DCHECK(it != pending_load_callback_.end()); for (size_t i = 0; i < it->second.size(); ++i) { ReadDirectoryCallbackState* callback_state = &it->second[i]; if (callback_state->entries_callback.is_null()) continue; // Filter out entries which were already sent. scoped_ptr entries_to_send(new ResourceEntryVector); for (size_t i = 0; i < entries.size(); ++i) { const ResourceEntry& entry = entries[i]; if (!callback_state->sent_entry_names.count(entry.base_name())) { callback_state->sent_entry_names.insert(entry.base_name()); entries_to_send->push_back(entry); } } callback_state->entries_callback.Run(entries_to_send.Pass()); } } void DirectoryLoader::LoadDirectoryFromServer( const DirectoryFetchInfo& directory_fetch_info) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!directory_fetch_info.empty()); DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString(); const google_apis::AboutResource* about_resource = about_resource_loader_->cached_about_resource(); DCHECK(about_resource); logger_->Log(logging::LOG_INFO, "Fast-fetch start: %s; Server changestamp: %s", directory_fetch_info.ToString().c_str(), base::Int64ToString( about_resource->largest_change_id()).c_str()); FeedFetcher* fetcher = new FeedFetcher(this, directory_fetch_info, about_resource->root_folder_id()); fast_fetch_feed_fetcher_set_.insert(fetcher); fetcher->Run( base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad, weak_ptr_factory_.GetWeakPtr(), directory_fetch_info, fetcher)); } void DirectoryLoader::LoadDirectoryFromServerAfterLoad( const DirectoryFetchInfo& directory_fetch_info, FeedFetcher* fetcher, FileError error) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!directory_fetch_info.empty()); // Delete the fetcher. fast_fetch_feed_fetcher_set_.erase(fetcher); delete fetcher; logger_->Log(logging::LOG_INFO, "Fast-fetch complete: %s => %s", directory_fetch_info.ToString().c_str(), FileErrorToString(error).c_str()); if (error != FILE_ERROR_OK) { LOG(ERROR) << "Failed to load directory: " << directory_fetch_info.local_id() << ": " << FileErrorToString(error); OnDirectoryLoadComplete(directory_fetch_info.local_id(), error); return; } // Update changestamp and get the directory path. base::FilePath* directory_path = new base::FilePath; base::PostTaskAndReplyWithResult( blocking_task_runner_.get(), FROM_HERE, base::Bind(&UpdateChangestamp, resource_metadata_, directory_fetch_info, directory_path), base::Bind( &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp, weak_ptr_factory_.GetWeakPtr(), directory_fetch_info, base::Owned(directory_path))); } void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp( const DirectoryFetchInfo& directory_fetch_info, const base::FilePath* directory_path, FileError error) { DCHECK(thread_checker_.CalledOnValidThread()); DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString(); OnDirectoryLoadComplete(directory_fetch_info.local_id(), error); // Also notify the observers. if (error == FILE_ERROR_OK && !directory_path->empty()) { FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_, OnDirectoryReloaded(*directory_path)); } } } // namespace internal } // namespace drive