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 *local_changestamp = resource_metadata->GetLargestChangestamp();
62 FileError UpdateChangestamp(ResourceMetadata* resource_metadata,
63 const DirectoryFetchInfo& directory_fetch_info,
64 base::FilePath* directory_path) {
65 // Update the directory changestamp.
66 ResourceEntry directory;
67 FileError error = resource_metadata->GetResourceEntryById(
68 directory_fetch_info.local_id(), &directory);
69 if (error != FILE_ERROR_OK)
72 if (!directory.file_info().is_directory())
73 return FILE_ERROR_NOT_A_DIRECTORY;
75 directory.mutable_directory_specific_info()->set_changestamp(
76 directory_fetch_info.changestamp());
77 error = resource_metadata->RefreshEntry(directory);
78 if (error != FILE_ERROR_OK)
81 // Get the directory path.
82 *directory_path = resource_metadata->GetFilePath(
83 directory_fetch_info.local_id());
89 struct DirectoryLoader::ReadDirectoryCallbackState {
90 ReadDirectoryEntriesCallback entries_callback;
91 FileOperationCallback completion_callback;
92 std::set<std::string> sent_entry_names;
95 // Fetches the resource entries in the directory with |directory_resource_id|.
96 class DirectoryLoader::FeedFetcher {
98 FeedFetcher(DirectoryLoader* loader,
99 const DirectoryFetchInfo& directory_fetch_info,
100 const std::string& root_folder_id)
102 directory_fetch_info_(directory_fetch_info),
103 root_folder_id_(root_folder_id),
104 weak_ptr_factory_(this) {
110 void Run(const FileOperationCallback& callback) {
111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
112 DCHECK(!callback.is_null());
113 DCHECK(!directory_fetch_info_.resource_id().empty());
115 // Remember the time stamp for usage stats.
116 start_time_ = base::TimeTicks::Now();
118 loader_->scheduler_->GetResourceListInDirectory(
119 directory_fetch_info_.resource_id(),
120 base::Bind(&FeedFetcher::OnResourceListFetched,
121 weak_ptr_factory_.GetWeakPtr(), callback));
125 void OnResourceListFetched(
126 const FileOperationCallback& callback,
127 google_apis::GDataErrorCode status,
128 scoped_ptr<google_apis::ResourceList> resource_list) {
129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
130 DCHECK(!callback.is_null());
132 FileError error = GDataToFileError(status);
133 if (error != FILE_ERROR_OK) {
138 DCHECK(resource_list);
139 scoped_ptr<ChangeList> change_list(new ChangeList(*resource_list));
142 resource_list->GetNextFeedURL(&next_url);
144 ResourceEntryVector* entries = new ResourceEntryVector;
145 loader_->loader_controller_->ScheduleRun(base::Bind(
147 &base::PostTaskAndReplyWithResult<FileError, FileError>),
148 loader_->blocking_task_runner_,
150 base::Bind(&ChangeListProcessor::RefreshDirectory,
151 loader_->resource_metadata_,
152 directory_fetch_info_,
153 base::Passed(&change_list),
155 base::Bind(&FeedFetcher::OnDirectoryRefreshed,
156 weak_ptr_factory_.GetWeakPtr(),
159 base::Owned(entries))));
162 void OnDirectoryRefreshed(
163 const FileOperationCallback& callback,
164 const GURL& next_url,
165 const std::vector<ResourceEntry>* refreshed_entries,
167 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
168 DCHECK(!callback.is_null());
170 if (error != FILE_ERROR_OK) {
175 loader_->SendEntries(directory_fetch_info_.local_id(), *refreshed_entries);
177 if (!next_url.is_empty()) {
178 // There is the remaining result so fetch it.
179 loader_->scheduler_->GetRemainingFileList(
181 base::Bind(&FeedFetcher::OnResourceListFetched,
182 weak_ptr_factory_.GetWeakPtr(), callback));
186 UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime",
187 base::TimeTicks::Now() - start_time_);
189 // Note: The fetcher is managed by DirectoryLoader, and the instance
190 // will be deleted in the callback. Do not touch the fields after this
192 callback.Run(FILE_ERROR_OK);
195 DirectoryLoader* loader_;
196 DirectoryFetchInfo directory_fetch_info_;
197 std::string root_folder_id_;
198 base::TimeTicks start_time_;
199 base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_;
200 DISALLOW_COPY_AND_ASSIGN(FeedFetcher);
203 DirectoryLoader::DirectoryLoader(
205 base::SequencedTaskRunner* blocking_task_runner,
206 ResourceMetadata* resource_metadata,
207 JobScheduler* scheduler,
208 AboutResourceLoader* about_resource_loader,
209 LoaderController* loader_controller)
211 blocking_task_runner_(blocking_task_runner),
212 resource_metadata_(resource_metadata),
213 scheduler_(scheduler),
214 about_resource_loader_(about_resource_loader),
215 loader_controller_(loader_controller),
216 weak_ptr_factory_(this) {
219 DirectoryLoader::~DirectoryLoader() {
220 STLDeleteElements(&fast_fetch_feed_fetcher_set_);
223 void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) {
224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
225 observers_.AddObserver(observer);
228 void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
229 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
230 observers_.RemoveObserver(observer);
233 void DirectoryLoader::ReadDirectory(
234 const base::FilePath& directory_path,
235 const ReadDirectoryEntriesCallback& entries_callback,
236 const FileOperationCallback& completion_callback) {
237 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
238 DCHECK(!completion_callback.is_null());
240 ResourceEntry* entry = new ResourceEntry;
241 base::PostTaskAndReplyWithResult(
242 blocking_task_runner_.get(),
244 base::Bind(&ResourceMetadata::GetResourceEntryByPath,
245 base::Unretained(resource_metadata_),
248 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
249 weak_ptr_factory_.GetWeakPtr(),
253 true, // should_try_loading_parent
254 base::Owned(entry)));
257 void DirectoryLoader::ReadDirectoryAfterGetEntry(
258 const base::FilePath& directory_path,
259 const ReadDirectoryEntriesCallback& entries_callback,
260 const FileOperationCallback& completion_callback,
261 bool should_try_loading_parent,
262 const ResourceEntry* entry,
264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
265 DCHECK(!completion_callback.is_null());
267 if (error == FILE_ERROR_NOT_FOUND &&
268 should_try_loading_parent &&
269 util::GetDriveGrandRootPath().IsParent(directory_path)) {
270 // This entry may be found after loading the parent.
271 ReadDirectory(directory_path.DirName(),
272 ReadDirectoryEntriesCallback(),
273 base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent,
274 weak_ptr_factory_.GetWeakPtr(),
277 completion_callback));
280 if (error != FILE_ERROR_OK) {
281 completion_callback.Run(error);
285 if (!entry->file_info().is_directory()) {
286 completion_callback.Run(FILE_ERROR_NOT_A_DIRECTORY);
290 DirectoryFetchInfo directory_fetch_info(
292 entry->resource_id(),
293 entry->directory_specific_info().changestamp());
295 // Register the callback function to be called when it is loaded.
296 const std::string& local_id = directory_fetch_info.local_id();
297 ReadDirectoryCallbackState callback_state;
298 callback_state.entries_callback = entries_callback;
299 callback_state.completion_callback = completion_callback;
300 pending_load_callback_[local_id].push_back(callback_state);
302 // If loading task for |local_id| is already running, do nothing.
303 if (pending_load_callback_[local_id].size() > 1)
306 // Note: To be precise, we need to call UpdateAboutResource() here. However,
307 // - It is costly to do GetAboutResource HTTP request every time.
308 // - The chance using an old value is small; it only happens when
309 // ReadDirectory is called during one GetAboutResource roundtrip time
310 // of a change list fetching.
311 // - Even if the value is old, it just marks the directory as older. It may
312 // trigger one future unnecessary re-fetch, but it'll never lose data.
313 about_resource_loader_->GetAboutResource(
314 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource,
315 weak_ptr_factory_.GetWeakPtr(), local_id));
318 void DirectoryLoader::ReadDirectoryAfterLoadParent(
319 const base::FilePath& directory_path,
320 const ReadDirectoryEntriesCallback& entries_callback,
321 const FileOperationCallback& completion_callback,
323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324 DCHECK(!completion_callback.is_null());
326 if (error != FILE_ERROR_OK) {
327 completion_callback.Run(error);
331 ResourceEntry* entry = new ResourceEntry;
332 base::PostTaskAndReplyWithResult(
333 blocking_task_runner_.get(),
335 base::Bind(&ResourceMetadata::GetResourceEntryByPath,
336 base::Unretained(resource_metadata_),
339 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
340 weak_ptr_factory_.GetWeakPtr(),
344 false, // should_try_loading_parent
345 base::Owned(entry)));
348 void DirectoryLoader::ReadDirectoryAfterGetAboutResource(
349 const std::string& local_id,
350 google_apis::GDataErrorCode status,
351 scoped_ptr<google_apis::AboutResource> about_resource) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354 FileError error = GDataToFileError(status);
355 if (error != FILE_ERROR_OK) {
356 OnDirectoryLoadComplete(local_id, error);
360 DCHECK(about_resource);
362 // Check the current status of local metadata, and start loading if needed.
363 google_apis::AboutResource* about_resource_ptr = about_resource.get();
364 ResourceEntry* entry = new ResourceEntry;
365 int64* local_changestamp = new int64;
366 base::PostTaskAndReplyWithResult(
367 blocking_task_runner_,
369 base::Bind(&CheckLocalState,
375 base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState,
376 weak_ptr_factory_.GetWeakPtr(),
377 base::Passed(&about_resource),
380 base::Owned(local_changestamp)));
383 void DirectoryLoader::ReadDirectoryAfterCheckLocalState(
384 scoped_ptr<google_apis::AboutResource> about_resource,
385 const std::string& local_id,
386 const ResourceEntry* entry,
387 const int64* local_changestamp,
389 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
390 DCHECK(about_resource);
392 if (error != FILE_ERROR_OK) {
393 OnDirectoryLoadComplete(local_id, error);
396 // This entry does not exist on the server.
397 if (entry->resource_id().empty()) {
398 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
402 int64 remote_changestamp = about_resource->largest_change_id();
404 // Start loading the directory.
405 int64 directory_changestamp = std::max(
406 entry->directory_specific_info().changestamp(), *local_changestamp);
408 DirectoryFetchInfo directory_fetch_info(
409 local_id, entry->resource_id(), remote_changestamp);
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);
416 // Start fetching the directory content, and mark it with the changestamp
417 // |remote_changestamp|.
418 LoadDirectoryFromServer(directory_fetch_info);
422 void DirectoryLoader::OnDirectoryLoadComplete(const std::string& local_id,
424 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
426 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
427 if (it == pending_load_callback_.end())
430 // No need to read metadata when no one needs entries.
431 bool needs_to_send_entries = false;
432 for (size_t i = 0; i < it->second.size(); ++i) {
433 const ReadDirectoryCallbackState& callback_state = it->second[i];
434 if (!callback_state.entries_callback.is_null())
435 needs_to_send_entries = true;
438 if (!needs_to_send_entries) {
439 OnDirectoryLoadCompleteAfterRead(local_id, NULL, FILE_ERROR_OK);
443 ResourceEntryVector* entries = new ResourceEntryVector;
444 base::PostTaskAndReplyWithResult(
445 blocking_task_runner_.get(),
447 base::Bind(&ResourceMetadata::ReadDirectoryById,
448 base::Unretained(resource_metadata_), local_id, entries),
449 base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead,
450 weak_ptr_factory_.GetWeakPtr(),
452 base::Owned(entries)));
455 void DirectoryLoader::OnDirectoryLoadCompleteAfterRead(
456 const std::string& local_id,
457 const ResourceEntryVector* entries,
459 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
460 if (it != pending_load_callback_.end()) {
461 DVLOG(1) << "Running callback for " << local_id;
463 if (error == FILE_ERROR_OK && entries)
464 SendEntries(local_id, *entries);
466 for (size_t i = 0; i < it->second.size(); ++i) {
467 const ReadDirectoryCallbackState& callback_state = it->second[i];
468 callback_state.completion_callback.Run(error);
470 pending_load_callback_.erase(it);
474 void DirectoryLoader::SendEntries(const std::string& local_id,
475 const ResourceEntryVector& entries) {
476 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
477 DCHECK(it != pending_load_callback_.end());
479 for (size_t i = 0; i < it->second.size(); ++i) {
480 ReadDirectoryCallbackState* callback_state = &it->second[i];
481 if (callback_state->entries_callback.is_null())
484 // Filter out entries which were already sent.
485 scoped_ptr<ResourceEntryVector> entries_to_send(new ResourceEntryVector);
486 for (size_t i = 0; i < entries.size(); ++i) {
487 const ResourceEntry& entry = entries[i];
488 if (!callback_state->sent_entry_names.count(entry.base_name())) {
489 callback_state->sent_entry_names.insert(entry.base_name());
490 entries_to_send->push_back(entry);
493 callback_state->entries_callback.Run(entries_to_send.Pass());
497 void DirectoryLoader::LoadDirectoryFromServer(
498 const DirectoryFetchInfo& directory_fetch_info) {
499 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
500 DCHECK(!directory_fetch_info.empty());
501 DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
503 const google_apis::AboutResource* about_resource =
504 about_resource_loader_->cached_about_resource();
505 DCHECK(about_resource);
507 logger_->Log(logging::LOG_INFO,
508 "Fast-fetch start: %s; Server changestamp: %s",
509 directory_fetch_info.ToString().c_str(),
511 about_resource->largest_change_id()).c_str());
513 FeedFetcher* fetcher = new FeedFetcher(this,
514 directory_fetch_info,
515 about_resource->root_folder_id());
516 fast_fetch_feed_fetcher_set_.insert(fetcher);
518 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad,
519 weak_ptr_factory_.GetWeakPtr(),
520 directory_fetch_info,
524 void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
525 const DirectoryFetchInfo& directory_fetch_info,
526 FeedFetcher* fetcher,
528 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
529 DCHECK(!directory_fetch_info.empty());
531 // Delete the fetcher.
532 fast_fetch_feed_fetcher_set_.erase(fetcher);
535 logger_->Log(logging::LOG_INFO,
536 "Fast-fetch complete: %s => %s",
537 directory_fetch_info.ToString().c_str(),
538 FileErrorToString(error).c_str());
540 if (error != FILE_ERROR_OK) {
541 LOG(ERROR) << "Failed to load directory: "
542 << directory_fetch_info.local_id()
543 << ": " << FileErrorToString(error);
544 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
548 // Update changestamp and get the directory path.
549 base::FilePath* directory_path = new base::FilePath;
550 base::PostTaskAndReplyWithResult(
551 blocking_task_runner_.get(),
553 base::Bind(&UpdateChangestamp,
555 directory_fetch_info,
558 &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp,
559 weak_ptr_factory_.GetWeakPtr(),
560 directory_fetch_info,
561 base::Owned(directory_path)));
564 void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp(
565 const DirectoryFetchInfo& directory_fetch_info,
566 const base::FilePath* directory_path,
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
570 DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
571 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
573 // Also notify the observers.
574 if (error == FILE_ERROR_OK && !directory_path->empty()) {
575 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
576 OnDirectoryChanged(*directory_path));
580 } // namespace internal