| // 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 "components/drive/chromeos/directory_loader.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/time/time.h" |
| #include "components/drive/chromeos/change_list_loader.h" |
| #include "components/drive/chromeos/change_list_loader_observer.h" |
| #include "components/drive/chromeos/change_list_processor.h" |
| #include "components/drive/chromeos/resource_metadata.h" |
| #include "components/drive/event_logger.h" |
| #include "components/drive/file_system_core_util.h" |
| #include "components/drive/job_scheduler.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_t* 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<std::string> 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, |
| std::unique_ptr<google_apis::FileList> 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); |
| std::unique_ptr<ChangeList> 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<FileError, FileError>), |
| base::RetainedRef(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<ResourceEntry>* 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<FeedFetcher> 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() { |
| } |
| |
| 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, |
| std::unique_ptr<google_apis::AboutResource> 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_t* local_changestamp = new int64_t; |
| 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( |
| std::unique_ptr<google_apis::AboutResource> about_resource, |
| const std::string& local_id, |
| const ResourceEntry* entry, |
| const int64_t* 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_t remote_changestamp = about_resource->largest_change_id(); |
| |
| // Start loading the directory. |
| int64_t 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. |
| std::unique_ptr<ResourceEntryVector> 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(std::move(entries_to_send)); |
| } |
| } |
| |
| 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(base::WrapUnique(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. |
| auto it = std::find_if(fast_fetch_feed_fetcher_set_.begin(), |
| fast_fetch_feed_fetcher_set_.end(), |
| [fetcher](const std::unique_ptr<FeedFetcher>& ptr) { |
| return ptr.get() == fetcher; |
| }); |
| fast_fetch_feed_fetcher_set_.erase(it); |
| |
| 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 (auto& observer : observers_) |
| observer.OnDirectoryReloaded(*directory_path); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace drive |