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.
5 #include "chrome/browser/chromeos/drive/directory_loader.h"
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/event_logger.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "google_apis/drive/drive_api_parser.h"
23 using content::BrowserThread;
30 // Minimum changestamp gap required to start loading directory.
31 const int kMinimumChangestampGap = 50;
33 FileError CheckLocalState(ResourceMetadata* resource_metadata,
34 const google_apis::AboutResource& about_resource,
35 const std::string& local_id,
37 int64* local_changestamp) {
38 // Fill My Drive resource ID.
39 ResourceEntry mydrive;
40 FileError error = resource_metadata->GetResourceEntryByPath(
41 util::GetDriveMyDriveRootPath(), &mydrive);
42 if (error != FILE_ERROR_OK)
45 if (mydrive.resource_id().empty()) {
46 mydrive.set_resource_id(about_resource.root_folder_id());
47 error = resource_metadata->RefreshEntry(mydrive);
48 if (error != FILE_ERROR_OK)
53 error = resource_metadata->GetResourceEntryById(local_id, entry);
54 if (error != FILE_ERROR_OK)
57 // Get the local changestamp.
58 return resource_metadata->GetLargestChangestamp(local_changestamp);
61 FileError UpdateChangestamp(ResourceMetadata* resource_metadata,
62 const DirectoryFetchInfo& directory_fetch_info,
63 base::FilePath* directory_path) {
64 // Update the directory changestamp.
65 ResourceEntry directory;
66 FileError error = resource_metadata->GetResourceEntryById(
67 directory_fetch_info.local_id(), &directory);
68 if (error != FILE_ERROR_OK)
71 if (!directory.file_info().is_directory())
72 return FILE_ERROR_NOT_A_DIRECTORY;
74 directory.mutable_directory_specific_info()->set_changestamp(
75 directory_fetch_info.changestamp());
76 error = resource_metadata->RefreshEntry(directory);
77 if (error != FILE_ERROR_OK)
80 // Get the directory path.
81 return resource_metadata->GetFilePath(directory_fetch_info.local_id(),
87 struct DirectoryLoader::ReadDirectoryCallbackState {
88 ReadDirectoryEntriesCallback entries_callback;
89 FileOperationCallback completion_callback;
90 std::set<std::string> sent_entry_names;
93 // Fetches the resource entries in the directory with |directory_resource_id|.
94 class DirectoryLoader::FeedFetcher {
96 FeedFetcher(DirectoryLoader* loader,
97 const DirectoryFetchInfo& directory_fetch_info,
98 const std::string& root_folder_id)
100 directory_fetch_info_(directory_fetch_info),
101 root_folder_id_(root_folder_id),
102 weak_ptr_factory_(this) {
108 void Run(const FileOperationCallback& callback) {
109 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
110 DCHECK(!callback.is_null());
111 DCHECK(!directory_fetch_info_.resource_id().empty());
113 // Remember the time stamp for usage stats.
114 start_time_ = base::TimeTicks::Now();
116 loader_->scheduler_->GetFileListInDirectory(
117 directory_fetch_info_.resource_id(),
118 base::Bind(&FeedFetcher::OnFileListFetched,
119 weak_ptr_factory_.GetWeakPtr(), callback));
123 void OnFileListFetched(const FileOperationCallback& callback,
124 google_apis::GDataErrorCode status,
125 scoped_ptr<google_apis::FileList> file_list) {
126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127 DCHECK(!callback.is_null());
129 FileError error = GDataToFileError(status);
130 if (error != FILE_ERROR_OK) {
136 scoped_ptr<ChangeList> change_list(new ChangeList(*file_list));
137 GURL next_url = file_list->next_link();
139 ResourceEntryVector* entries = new ResourceEntryVector;
140 loader_->loader_controller_->ScheduleRun(base::Bind(
142 &base::PostTaskAndReplyWithResult<FileError, FileError>),
143 loader_->blocking_task_runner_,
145 base::Bind(&ChangeListProcessor::RefreshDirectory,
146 loader_->resource_metadata_,
147 directory_fetch_info_,
148 base::Passed(&change_list),
150 base::Bind(&FeedFetcher::OnDirectoryRefreshed,
151 weak_ptr_factory_.GetWeakPtr(),
154 base::Owned(entries))));
157 void OnDirectoryRefreshed(
158 const FileOperationCallback& callback,
159 const GURL& next_url,
160 const std::vector<ResourceEntry>* refreshed_entries,
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
163 DCHECK(!callback.is_null());
165 if (error != FILE_ERROR_OK) {
170 loader_->SendEntries(directory_fetch_info_.local_id(), *refreshed_entries);
172 if (!next_url.is_empty()) {
173 // There is the remaining result so fetch it.
174 loader_->scheduler_->GetRemainingFileList(
176 base::Bind(&FeedFetcher::OnFileListFetched,
177 weak_ptr_factory_.GetWeakPtr(), callback));
181 UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime",
182 base::TimeTicks::Now() - start_time_);
184 // Note: The fetcher is managed by DirectoryLoader, and the instance
185 // will be deleted in the callback. Do not touch the fields after this
187 callback.Run(FILE_ERROR_OK);
190 DirectoryLoader* loader_;
191 DirectoryFetchInfo directory_fetch_info_;
192 std::string root_folder_id_;
193 base::TimeTicks start_time_;
194 base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_;
195 DISALLOW_COPY_AND_ASSIGN(FeedFetcher);
198 DirectoryLoader::DirectoryLoader(
200 base::SequencedTaskRunner* blocking_task_runner,
201 ResourceMetadata* resource_metadata,
202 JobScheduler* scheduler,
203 AboutResourceLoader* about_resource_loader,
204 LoaderController* loader_controller)
206 blocking_task_runner_(blocking_task_runner),
207 resource_metadata_(resource_metadata),
208 scheduler_(scheduler),
209 about_resource_loader_(about_resource_loader),
210 loader_controller_(loader_controller),
211 weak_ptr_factory_(this) {
214 DirectoryLoader::~DirectoryLoader() {
215 STLDeleteElements(&fast_fetch_feed_fetcher_set_);
218 void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) {
219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
220 observers_.AddObserver(observer);
223 void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
225 observers_.RemoveObserver(observer);
228 void DirectoryLoader::ReadDirectory(
229 const base::FilePath& directory_path,
230 const ReadDirectoryEntriesCallback& entries_callback,
231 const FileOperationCallback& completion_callback) {
232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
233 DCHECK(!completion_callback.is_null());
235 ResourceEntry* entry = new ResourceEntry;
236 base::PostTaskAndReplyWithResult(
237 blocking_task_runner_.get(),
239 base::Bind(&ResourceMetadata::GetResourceEntryByPath,
240 base::Unretained(resource_metadata_),
243 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
244 weak_ptr_factory_.GetWeakPtr(),
248 true, // should_try_loading_parent
249 base::Owned(entry)));
252 void DirectoryLoader::ReadDirectoryAfterGetEntry(
253 const base::FilePath& directory_path,
254 const ReadDirectoryEntriesCallback& entries_callback,
255 const FileOperationCallback& completion_callback,
256 bool should_try_loading_parent,
257 const ResourceEntry* entry,
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
260 DCHECK(!completion_callback.is_null());
262 if (error == FILE_ERROR_NOT_FOUND &&
263 should_try_loading_parent &&
264 util::GetDriveGrandRootPath().IsParent(directory_path)) {
265 // This entry may be found after loading the parent.
266 ReadDirectory(directory_path.DirName(),
267 ReadDirectoryEntriesCallback(),
268 base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent,
269 weak_ptr_factory_.GetWeakPtr(),
272 completion_callback));
275 if (error != FILE_ERROR_OK) {
276 completion_callback.Run(error);
280 if (!entry->file_info().is_directory()) {
281 completion_callback.Run(FILE_ERROR_NOT_A_DIRECTORY);
285 DirectoryFetchInfo directory_fetch_info(
287 entry->resource_id(),
288 entry->directory_specific_info().changestamp());
290 // Register the callback function to be called when it is loaded.
291 const std::string& local_id = directory_fetch_info.local_id();
292 ReadDirectoryCallbackState callback_state;
293 callback_state.entries_callback = entries_callback;
294 callback_state.completion_callback = completion_callback;
295 pending_load_callback_[local_id].push_back(callback_state);
297 // If loading task for |local_id| is already running, do nothing.
298 if (pending_load_callback_[local_id].size() > 1)
301 // Note: To be precise, we need to call UpdateAboutResource() here. However,
302 // - It is costly to do GetAboutResource HTTP request every time.
303 // - The chance using an old value is small; it only happens when
304 // ReadDirectory is called during one GetAboutResource roundtrip time
305 // of a change list fetching.
306 // - Even if the value is old, it just marks the directory as older. It may
307 // trigger one future unnecessary re-fetch, but it'll never lose data.
308 about_resource_loader_->GetAboutResource(
309 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource,
310 weak_ptr_factory_.GetWeakPtr(), local_id));
313 void DirectoryLoader::ReadDirectoryAfterLoadParent(
314 const base::FilePath& directory_path,
315 const ReadDirectoryEntriesCallback& entries_callback,
316 const FileOperationCallback& completion_callback,
318 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
319 DCHECK(!completion_callback.is_null());
321 if (error != FILE_ERROR_OK) {
322 completion_callback.Run(error);
326 ResourceEntry* entry = new ResourceEntry;
327 base::PostTaskAndReplyWithResult(
328 blocking_task_runner_.get(),
330 base::Bind(&ResourceMetadata::GetResourceEntryByPath,
331 base::Unretained(resource_metadata_),
334 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
335 weak_ptr_factory_.GetWeakPtr(),
339 false, // should_try_loading_parent
340 base::Owned(entry)));
343 void DirectoryLoader::ReadDirectoryAfterGetAboutResource(
344 const std::string& local_id,
345 google_apis::GDataErrorCode status,
346 scoped_ptr<google_apis::AboutResource> about_resource) {
347 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349 FileError error = GDataToFileError(status);
350 if (error != FILE_ERROR_OK) {
351 OnDirectoryLoadComplete(local_id, error);
355 DCHECK(about_resource);
357 // Check the current status of local metadata, and start loading if needed.
358 google_apis::AboutResource* about_resource_ptr = about_resource.get();
359 ResourceEntry* entry = new ResourceEntry;
360 int64* local_changestamp = new int64;
361 base::PostTaskAndReplyWithResult(
362 blocking_task_runner_,
364 base::Bind(&CheckLocalState,
370 base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState,
371 weak_ptr_factory_.GetWeakPtr(),
372 base::Passed(&about_resource),
375 base::Owned(local_changestamp)));
378 void DirectoryLoader::ReadDirectoryAfterCheckLocalState(
379 scoped_ptr<google_apis::AboutResource> about_resource,
380 const std::string& local_id,
381 const ResourceEntry* entry,
382 const int64* local_changestamp,
384 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
385 DCHECK(about_resource);
387 if (error != FILE_ERROR_OK) {
388 OnDirectoryLoadComplete(local_id, error);
391 // This entry does not exist on the server.
392 if (entry->resource_id().empty()) {
393 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
397 int64 remote_changestamp = about_resource->largest_change_id();
399 // Start loading the directory.
400 int64 directory_changestamp = std::max(
401 entry->directory_specific_info().changestamp(), *local_changestamp);
403 DirectoryFetchInfo directory_fetch_info(
404 local_id, entry->resource_id(), remote_changestamp);
406 // If the directory's changestamp is new enough, just schedule to run the
407 // callback, as there is no need to fetch the directory.
408 if (directory_changestamp + kMinimumChangestampGap > remote_changestamp) {
409 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
411 // Start fetching the directory content, and mark it with the changestamp
412 // |remote_changestamp|.
413 LoadDirectoryFromServer(directory_fetch_info);
417 void DirectoryLoader::OnDirectoryLoadComplete(const std::string& local_id,
419 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
421 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
422 if (it == pending_load_callback_.end())
425 // No need to read metadata when no one needs entries.
426 bool needs_to_send_entries = false;
427 for (size_t i = 0; i < it->second.size(); ++i) {
428 const ReadDirectoryCallbackState& callback_state = it->second[i];
429 if (!callback_state.entries_callback.is_null())
430 needs_to_send_entries = true;
433 if (!needs_to_send_entries) {
434 OnDirectoryLoadCompleteAfterRead(local_id, NULL, FILE_ERROR_OK);
438 ResourceEntryVector* entries = new ResourceEntryVector;
439 base::PostTaskAndReplyWithResult(
440 blocking_task_runner_.get(),
442 base::Bind(&ResourceMetadata::ReadDirectoryById,
443 base::Unretained(resource_metadata_), local_id, entries),
444 base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead,
445 weak_ptr_factory_.GetWeakPtr(),
447 base::Owned(entries)));
450 void DirectoryLoader::OnDirectoryLoadCompleteAfterRead(
451 const std::string& local_id,
452 const ResourceEntryVector* entries,
454 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
455 if (it != pending_load_callback_.end()) {
456 DVLOG(1) << "Running callback for " << local_id;
458 if (error == FILE_ERROR_OK && entries)
459 SendEntries(local_id, *entries);
461 for (size_t i = 0; i < it->second.size(); ++i) {
462 const ReadDirectoryCallbackState& callback_state = it->second[i];
463 callback_state.completion_callback.Run(error);
465 pending_load_callback_.erase(it);
469 void DirectoryLoader::SendEntries(const std::string& local_id,
470 const ResourceEntryVector& entries) {
471 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
472 DCHECK(it != pending_load_callback_.end());
474 for (size_t i = 0; i < it->second.size(); ++i) {
475 ReadDirectoryCallbackState* callback_state = &it->second[i];
476 if (callback_state->entries_callback.is_null())
479 // Filter out entries which were already sent.
480 scoped_ptr<ResourceEntryVector> entries_to_send(new ResourceEntryVector);
481 for (size_t i = 0; i < entries.size(); ++i) {
482 const ResourceEntry& entry = entries[i];
483 if (!callback_state->sent_entry_names.count(entry.base_name())) {
484 callback_state->sent_entry_names.insert(entry.base_name());
485 entries_to_send->push_back(entry);
488 callback_state->entries_callback.Run(entries_to_send.Pass());
492 void DirectoryLoader::LoadDirectoryFromServer(
493 const DirectoryFetchInfo& directory_fetch_info) {
494 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
495 DCHECK(!directory_fetch_info.empty());
496 DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
498 const google_apis::AboutResource* about_resource =
499 about_resource_loader_->cached_about_resource();
500 DCHECK(about_resource);
502 logger_->Log(logging::LOG_INFO,
503 "Fast-fetch start: %s; Server changestamp: %s",
504 directory_fetch_info.ToString().c_str(),
506 about_resource->largest_change_id()).c_str());
508 FeedFetcher* fetcher = new FeedFetcher(this,
509 directory_fetch_info,
510 about_resource->root_folder_id());
511 fast_fetch_feed_fetcher_set_.insert(fetcher);
513 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad,
514 weak_ptr_factory_.GetWeakPtr(),
515 directory_fetch_info,
519 void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
520 const DirectoryFetchInfo& directory_fetch_info,
521 FeedFetcher* fetcher,
523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
524 DCHECK(!directory_fetch_info.empty());
526 // Delete the fetcher.
527 fast_fetch_feed_fetcher_set_.erase(fetcher);
530 logger_->Log(logging::LOG_INFO,
531 "Fast-fetch complete: %s => %s",
532 directory_fetch_info.ToString().c_str(),
533 FileErrorToString(error).c_str());
535 if (error != FILE_ERROR_OK) {
536 LOG(ERROR) << "Failed to load directory: "
537 << directory_fetch_info.local_id()
538 << ": " << FileErrorToString(error);
539 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
543 // Update changestamp and get the directory path.
544 base::FilePath* directory_path = new base::FilePath;
545 base::PostTaskAndReplyWithResult(
546 blocking_task_runner_.get(),
548 base::Bind(&UpdateChangestamp,
550 directory_fetch_info,
553 &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp,
554 weak_ptr_factory_.GetWeakPtr(),
555 directory_fetch_info,
556 base::Owned(directory_path)));
559 void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp(
560 const DirectoryFetchInfo& directory_fetch_info,
561 const base::FilePath* directory_path,
563 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
565 DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
566 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
568 // Also notify the observers.
569 if (error == FILE_ERROR_OK && !directory_path->empty()) {
570 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
571 OnDirectoryChanged(*directory_path));
575 } // namespace internal