Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / drive / directory_loader.cc
blobcd0f049e6d645aef830c4bc0b0872c00ddcec036
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 "components/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 "components/drive/change_list_loader.h"
13 #include "components/drive/change_list_loader_observer.h"
14 #include "components/drive/change_list_processor.h"
15 #include "components/drive/event_logger.h"
16 #include "components/drive/file_system_core_util.h"
17 #include "components/drive/job_scheduler.h"
18 #include "components/drive/resource_metadata.h"
19 #include "google_apis/drive/drive_api_parser.h"
20 #include "url/gurl.h"
22 namespace drive {
23 namespace internal {
25 namespace {
27 // Minimum changestamp gap required to start loading directory.
28 const int kMinimumChangestampGap = 50;
30 FileError CheckLocalState(ResourceMetadata* resource_metadata,
31 const google_apis::AboutResource& about_resource,
32 const std::string& local_id,
33 ResourceEntry* entry,
34 int64* local_changestamp) {
35 // Fill My Drive resource ID.
36 ResourceEntry mydrive;
37 FileError error = resource_metadata->GetResourceEntryByPath(
38 util::GetDriveMyDriveRootPath(), &mydrive);
39 if (error != FILE_ERROR_OK)
40 return error;
42 if (mydrive.resource_id().empty()) {
43 mydrive.set_resource_id(about_resource.root_folder_id());
44 error = resource_metadata->RefreshEntry(mydrive);
45 if (error != FILE_ERROR_OK)
46 return error;
49 // Get entry.
50 error = resource_metadata->GetResourceEntryById(local_id, entry);
51 if (error != FILE_ERROR_OK)
52 return error;
54 // Get the local changestamp.
55 return resource_metadata->GetLargestChangestamp(local_changestamp);
58 FileError UpdateChangestamp(ResourceMetadata* resource_metadata,
59 const DirectoryFetchInfo& directory_fetch_info,
60 base::FilePath* directory_path) {
61 // Update the directory changestamp.
62 ResourceEntry directory;
63 FileError error = resource_metadata->GetResourceEntryById(
64 directory_fetch_info.local_id(), &directory);
65 if (error != FILE_ERROR_OK)
66 return error;
68 if (!directory.file_info().is_directory())
69 return FILE_ERROR_NOT_A_DIRECTORY;
71 directory.mutable_directory_specific_info()->set_changestamp(
72 directory_fetch_info.changestamp());
73 error = resource_metadata->RefreshEntry(directory);
74 if (error != FILE_ERROR_OK)
75 return error;
77 // Get the directory path.
78 return resource_metadata->GetFilePath(directory_fetch_info.local_id(),
79 directory_path);
82 } // namespace
84 struct DirectoryLoader::ReadDirectoryCallbackState {
85 ReadDirectoryEntriesCallback entries_callback;
86 FileOperationCallback completion_callback;
87 std::set<std::string> sent_entry_names;
90 // Fetches the resource entries in the directory with |directory_resource_id|.
91 class DirectoryLoader::FeedFetcher {
92 public:
93 FeedFetcher(DirectoryLoader* loader,
94 const DirectoryFetchInfo& directory_fetch_info,
95 const std::string& root_folder_id)
96 : loader_(loader),
97 directory_fetch_info_(directory_fetch_info),
98 root_folder_id_(root_folder_id),
99 weak_ptr_factory_(this) {
102 ~FeedFetcher() {
105 void Run(const FileOperationCallback& callback) {
106 DCHECK(thread_checker_.CalledOnValidThread());
107 DCHECK(!callback.is_null());
108 DCHECK(!directory_fetch_info_.resource_id().empty());
110 // Remember the time stamp for usage stats.
111 start_time_ = base::TimeTicks::Now();
113 loader_->scheduler_->GetFileListInDirectory(
114 directory_fetch_info_.resource_id(),
115 base::Bind(&FeedFetcher::OnFileListFetched,
116 weak_ptr_factory_.GetWeakPtr(), callback));
119 private:
120 void OnFileListFetched(const FileOperationCallback& callback,
121 google_apis::DriveApiErrorCode status,
122 scoped_ptr<google_apis::FileList> file_list) {
123 DCHECK(thread_checker_.CalledOnValidThread());
124 DCHECK(!callback.is_null());
126 FileError error = GDataToFileError(status);
127 if (error != FILE_ERROR_OK) {
128 callback.Run(error);
129 return;
132 DCHECK(file_list);
133 scoped_ptr<ChangeList> change_list(new ChangeList(*file_list));
134 GURL next_url = file_list->next_link();
136 ResourceEntryVector* entries = new ResourceEntryVector;
137 loader_->loader_controller_->ScheduleRun(base::Bind(
138 base::IgnoreResult(
139 &base::PostTaskAndReplyWithResult<FileError, FileError>),
140 loader_->blocking_task_runner_,
141 FROM_HERE,
142 base::Bind(&ChangeListProcessor::RefreshDirectory,
143 loader_->resource_metadata_,
144 directory_fetch_info_,
145 base::Passed(&change_list),
146 entries),
147 base::Bind(&FeedFetcher::OnDirectoryRefreshed,
148 weak_ptr_factory_.GetWeakPtr(),
149 callback,
150 next_url,
151 base::Owned(entries))));
154 void OnDirectoryRefreshed(
155 const FileOperationCallback& callback,
156 const GURL& next_url,
157 const std::vector<ResourceEntry>* refreshed_entries,
158 FileError error) {
159 DCHECK(thread_checker_.CalledOnValidThread());
160 DCHECK(!callback.is_null());
162 if (error != FILE_ERROR_OK) {
163 callback.Run(error);
164 return;
167 loader_->SendEntries(directory_fetch_info_.local_id(), *refreshed_entries);
169 if (!next_url.is_empty()) {
170 // There is the remaining result so fetch it.
171 loader_->scheduler_->GetRemainingFileList(
172 next_url,
173 base::Bind(&FeedFetcher::OnFileListFetched,
174 weak_ptr_factory_.GetWeakPtr(), callback));
175 return;
178 UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime",
179 base::TimeTicks::Now() - start_time_);
181 // Note: The fetcher is managed by DirectoryLoader, and the instance
182 // will be deleted in the callback. Do not touch the fields after this
183 // invocation.
184 callback.Run(FILE_ERROR_OK);
187 DirectoryLoader* loader_;
188 DirectoryFetchInfo directory_fetch_info_;
189 std::string root_folder_id_;
190 base::TimeTicks start_time_;
191 base::ThreadChecker thread_checker_;
192 base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_;
193 DISALLOW_COPY_AND_ASSIGN(FeedFetcher);
196 DirectoryLoader::DirectoryLoader(
197 EventLogger* logger,
198 base::SequencedTaskRunner* blocking_task_runner,
199 ResourceMetadata* resource_metadata,
200 JobScheduler* scheduler,
201 AboutResourceLoader* about_resource_loader,
202 LoaderController* loader_controller)
203 : logger_(logger),
204 blocking_task_runner_(blocking_task_runner),
205 resource_metadata_(resource_metadata),
206 scheduler_(scheduler),
207 about_resource_loader_(about_resource_loader),
208 loader_controller_(loader_controller),
209 weak_ptr_factory_(this) {
212 DirectoryLoader::~DirectoryLoader() {
213 STLDeleteElements(&fast_fetch_feed_fetcher_set_);
216 void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) {
217 DCHECK(thread_checker_.CalledOnValidThread());
218 observers_.AddObserver(observer);
221 void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
222 DCHECK(thread_checker_.CalledOnValidThread());
223 observers_.RemoveObserver(observer);
226 void DirectoryLoader::ReadDirectory(
227 const base::FilePath& directory_path,
228 const ReadDirectoryEntriesCallback& entries_callback,
229 const FileOperationCallback& completion_callback) {
230 DCHECK(thread_checker_.CalledOnValidThread());
231 DCHECK(!completion_callback.is_null());
233 ResourceEntry* entry = new ResourceEntry;
234 base::PostTaskAndReplyWithResult(
235 blocking_task_runner_.get(),
236 FROM_HERE,
237 base::Bind(&ResourceMetadata::GetResourceEntryByPath,
238 base::Unretained(resource_metadata_),
239 directory_path,
240 entry),
241 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
242 weak_ptr_factory_.GetWeakPtr(),
243 directory_path,
244 entries_callback,
245 completion_callback,
246 true, // should_try_loading_parent
247 base::Owned(entry)));
250 void DirectoryLoader::ReadDirectoryAfterGetEntry(
251 const base::FilePath& directory_path,
252 const ReadDirectoryEntriesCallback& entries_callback,
253 const FileOperationCallback& completion_callback,
254 bool should_try_loading_parent,
255 const ResourceEntry* entry,
256 FileError error) {
257 DCHECK(thread_checker_.CalledOnValidThread());
258 DCHECK(!completion_callback.is_null());
260 if (error == FILE_ERROR_NOT_FOUND &&
261 should_try_loading_parent &&
262 util::GetDriveGrandRootPath().IsParent(directory_path)) {
263 // This entry may be found after loading the parent.
264 ReadDirectory(directory_path.DirName(),
265 ReadDirectoryEntriesCallback(),
266 base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent,
267 weak_ptr_factory_.GetWeakPtr(),
268 directory_path,
269 entries_callback,
270 completion_callback));
271 return;
273 if (error != FILE_ERROR_OK) {
274 completion_callback.Run(error);
275 return;
278 if (!entry->file_info().is_directory()) {
279 completion_callback.Run(FILE_ERROR_NOT_A_DIRECTORY);
280 return;
283 DirectoryFetchInfo directory_fetch_info(
284 entry->local_id(),
285 entry->resource_id(),
286 entry->directory_specific_info().changestamp());
288 // Register the callback function to be called when it is loaded.
289 const std::string& local_id = directory_fetch_info.local_id();
290 ReadDirectoryCallbackState callback_state;
291 callback_state.entries_callback = entries_callback;
292 callback_state.completion_callback = completion_callback;
293 pending_load_callback_[local_id].push_back(callback_state);
295 // If loading task for |local_id| is already running, do nothing.
296 if (pending_load_callback_[local_id].size() > 1)
297 return;
299 about_resource_loader_->GetAboutResource(
300 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource,
301 weak_ptr_factory_.GetWeakPtr(), local_id));
304 void DirectoryLoader::ReadDirectoryAfterLoadParent(
305 const base::FilePath& directory_path,
306 const ReadDirectoryEntriesCallback& entries_callback,
307 const FileOperationCallback& completion_callback,
308 FileError error) {
309 DCHECK(thread_checker_.CalledOnValidThread());
310 DCHECK(!completion_callback.is_null());
312 if (error != FILE_ERROR_OK) {
313 completion_callback.Run(error);
314 return;
317 ResourceEntry* entry = new ResourceEntry;
318 base::PostTaskAndReplyWithResult(
319 blocking_task_runner_.get(),
320 FROM_HERE,
321 base::Bind(&ResourceMetadata::GetResourceEntryByPath,
322 base::Unretained(resource_metadata_),
323 directory_path,
324 entry),
325 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
326 weak_ptr_factory_.GetWeakPtr(),
327 directory_path,
328 entries_callback,
329 completion_callback,
330 false, // should_try_loading_parent
331 base::Owned(entry)));
334 void DirectoryLoader::ReadDirectoryAfterGetAboutResource(
335 const std::string& local_id,
336 google_apis::DriveApiErrorCode status,
337 scoped_ptr<google_apis::AboutResource> about_resource) {
338 DCHECK(thread_checker_.CalledOnValidThread());
340 FileError error = GDataToFileError(status);
341 if (error != FILE_ERROR_OK) {
342 OnDirectoryLoadComplete(local_id, error);
343 return;
346 DCHECK(about_resource);
348 // Check the current status of local metadata, and start loading if needed.
349 google_apis::AboutResource* about_resource_ptr = about_resource.get();
350 ResourceEntry* entry = new ResourceEntry;
351 int64* local_changestamp = new int64;
352 base::PostTaskAndReplyWithResult(
353 blocking_task_runner_.get(),
354 FROM_HERE,
355 base::Bind(&CheckLocalState,
356 resource_metadata_,
357 *about_resource_ptr,
358 local_id,
359 entry,
360 local_changestamp),
361 base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState,
362 weak_ptr_factory_.GetWeakPtr(),
363 base::Passed(&about_resource),
364 local_id,
365 base::Owned(entry),
366 base::Owned(local_changestamp)));
369 void DirectoryLoader::ReadDirectoryAfterCheckLocalState(
370 scoped_ptr<google_apis::AboutResource> about_resource,
371 const std::string& local_id,
372 const ResourceEntry* entry,
373 const int64* local_changestamp,
374 FileError error) {
375 DCHECK(thread_checker_.CalledOnValidThread());
376 DCHECK(about_resource);
378 if (error != FILE_ERROR_OK) {
379 OnDirectoryLoadComplete(local_id, error);
380 return;
382 // This entry does not exist on the server.
383 if (entry->resource_id().empty()) {
384 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
385 return;
388 int64 remote_changestamp = about_resource->largest_change_id();
390 // Start loading the directory.
391 int64 directory_changestamp = std::max(
392 entry->directory_specific_info().changestamp(), *local_changestamp);
394 DirectoryFetchInfo directory_fetch_info(
395 local_id, entry->resource_id(), remote_changestamp);
397 // If the directory's changestamp is up-to-date or the global changestamp of
398 // the metadata DB is new enough (which means the normal changelist loading
399 // should finish very soon), just schedule to run the callback, as there is no
400 // need to fetch the directory.
401 if (directory_changestamp >= remote_changestamp ||
402 *local_changestamp + kMinimumChangestampGap > remote_changestamp) {
403 OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
404 } else {
405 // Start fetching the directory content, and mark it with the changestamp
406 // |remote_changestamp|.
407 LoadDirectoryFromServer(directory_fetch_info);
411 void DirectoryLoader::OnDirectoryLoadComplete(const std::string& local_id,
412 FileError error) {
413 DCHECK(thread_checker_.CalledOnValidThread());
415 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
416 if (it == pending_load_callback_.end())
417 return;
419 // No need to read metadata when no one needs entries.
420 bool needs_to_send_entries = false;
421 for (size_t i = 0; i < it->second.size(); ++i) {
422 const ReadDirectoryCallbackState& callback_state = it->second[i];
423 if (!callback_state.entries_callback.is_null())
424 needs_to_send_entries = true;
427 if (!needs_to_send_entries) {
428 OnDirectoryLoadCompleteAfterRead(local_id, NULL, FILE_ERROR_OK);
429 return;
432 ResourceEntryVector* entries = new ResourceEntryVector;
433 base::PostTaskAndReplyWithResult(
434 blocking_task_runner_.get(),
435 FROM_HERE,
436 base::Bind(&ResourceMetadata::ReadDirectoryById,
437 base::Unretained(resource_metadata_), local_id, entries),
438 base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead,
439 weak_ptr_factory_.GetWeakPtr(),
440 local_id,
441 base::Owned(entries)));
444 void DirectoryLoader::OnDirectoryLoadCompleteAfterRead(
445 const std::string& local_id,
446 const ResourceEntryVector* entries,
447 FileError error) {
448 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
449 if (it != pending_load_callback_.end()) {
450 DVLOG(1) << "Running callback for " << local_id;
452 if (error == FILE_ERROR_OK && entries)
453 SendEntries(local_id, *entries);
455 for (size_t i = 0; i < it->second.size(); ++i) {
456 const ReadDirectoryCallbackState& callback_state = it->second[i];
457 callback_state.completion_callback.Run(error);
459 pending_load_callback_.erase(it);
463 void DirectoryLoader::SendEntries(const std::string& local_id,
464 const ResourceEntryVector& entries) {
465 LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
466 DCHECK(it != pending_load_callback_.end());
468 for (size_t i = 0; i < it->second.size(); ++i) {
469 ReadDirectoryCallbackState* callback_state = &it->second[i];
470 if (callback_state->entries_callback.is_null())
471 continue;
473 // Filter out entries which were already sent.
474 scoped_ptr<ResourceEntryVector> entries_to_send(new ResourceEntryVector);
475 for (size_t i = 0; i < entries.size(); ++i) {
476 const ResourceEntry& entry = entries[i];
477 if (!callback_state->sent_entry_names.count(entry.base_name())) {
478 callback_state->sent_entry_names.insert(entry.base_name());
479 entries_to_send->push_back(entry);
482 callback_state->entries_callback.Run(entries_to_send.Pass());
486 void DirectoryLoader::LoadDirectoryFromServer(
487 const DirectoryFetchInfo& directory_fetch_info) {
488 DCHECK(thread_checker_.CalledOnValidThread());
489 DCHECK(!directory_fetch_info.empty());
490 DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
492 const google_apis::AboutResource* about_resource =
493 about_resource_loader_->cached_about_resource();
494 DCHECK(about_resource);
496 logger_->Log(logging::LOG_INFO,
497 "Fast-fetch start: %s; Server changestamp: %s",
498 directory_fetch_info.ToString().c_str(),
499 base::Int64ToString(
500 about_resource->largest_change_id()).c_str());
502 FeedFetcher* fetcher = new FeedFetcher(this,
503 directory_fetch_info,
504 about_resource->root_folder_id());
505 fast_fetch_feed_fetcher_set_.insert(fetcher);
506 fetcher->Run(
507 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad,
508 weak_ptr_factory_.GetWeakPtr(),
509 directory_fetch_info,
510 fetcher));
513 void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
514 const DirectoryFetchInfo& directory_fetch_info,
515 FeedFetcher* fetcher,
516 FileError error) {
517 DCHECK(thread_checker_.CalledOnValidThread());
518 DCHECK(!directory_fetch_info.empty());
520 // Delete the fetcher.
521 fast_fetch_feed_fetcher_set_.erase(fetcher);
522 delete fetcher;
524 logger_->Log(logging::LOG_INFO,
525 "Fast-fetch complete: %s => %s",
526 directory_fetch_info.ToString().c_str(),
527 FileErrorToString(error).c_str());
529 if (error != FILE_ERROR_OK) {
530 LOG(ERROR) << "Failed to load directory: "
531 << directory_fetch_info.local_id()
532 << ": " << FileErrorToString(error);
533 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
534 return;
537 // Update changestamp and get the directory path.
538 base::FilePath* directory_path = new base::FilePath;
539 base::PostTaskAndReplyWithResult(
540 blocking_task_runner_.get(),
541 FROM_HERE,
542 base::Bind(&UpdateChangestamp,
543 resource_metadata_,
544 directory_fetch_info,
545 directory_path),
546 base::Bind(
547 &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp,
548 weak_ptr_factory_.GetWeakPtr(),
549 directory_fetch_info,
550 base::Owned(directory_path)));
553 void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp(
554 const DirectoryFetchInfo& directory_fetch_info,
555 const base::FilePath* directory_path,
556 FileError error) {
557 DCHECK(thread_checker_.CalledOnValidThread());
559 DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
560 OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
562 // Also notify the observers.
563 if (error == FILE_ERROR_OK && !directory_path->empty()) {
564 FOR_EACH_OBSERVER(ChangeListLoaderObserver,
565 observers_,
566 OnDirectoryReloaded(*directory_path));
570 } // namespace internal
571 } // namespace drive