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 "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"
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
,
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
)
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
)
50 error
= resource_metadata
->GetResourceEntryById(local_id
, entry
);
51 if (error
!= FILE_ERROR_OK
)
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
)
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
)
77 // Get the directory path.
78 return resource_metadata
->GetFilePath(directory_fetch_info
.local_id(),
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
{
93 FeedFetcher(DirectoryLoader
* loader
,
94 const DirectoryFetchInfo
& directory_fetch_info
,
95 const std::string
& root_folder_id
)
97 directory_fetch_info_(directory_fetch_info
),
98 root_folder_id_(root_folder_id
),
99 weak_ptr_factory_(this) {
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
));
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
) {
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(
139 &base::PostTaskAndReplyWithResult
<FileError
, FileError
>),
140 loader_
->blocking_task_runner_
,
142 base::Bind(&ChangeListProcessor::RefreshDirectory
,
143 loader_
->resource_metadata_
,
144 directory_fetch_info_
,
145 base::Passed(&change_list
),
147 base::Bind(&FeedFetcher::OnDirectoryRefreshed
,
148 weak_ptr_factory_
.GetWeakPtr(),
151 base::Owned(entries
))));
154 void OnDirectoryRefreshed(
155 const FileOperationCallback
& callback
,
156 const GURL
& next_url
,
157 const std::vector
<ResourceEntry
>* refreshed_entries
,
159 DCHECK(thread_checker_
.CalledOnValidThread());
160 DCHECK(!callback
.is_null());
162 if (error
!= FILE_ERROR_OK
) {
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(
173 base::Bind(&FeedFetcher::OnFileListFetched
,
174 weak_ptr_factory_
.GetWeakPtr(), callback
));
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
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(
198 base::SequencedTaskRunner
* blocking_task_runner
,
199 ResourceMetadata
* resource_metadata
,
200 JobScheduler
* scheduler
,
201 AboutResourceLoader
* about_resource_loader
,
202 LoaderController
* loader_controller
)
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(),
237 base::Bind(&ResourceMetadata::GetResourceEntryByPath
,
238 base::Unretained(resource_metadata_
),
241 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry
,
242 weak_ptr_factory_
.GetWeakPtr(),
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
,
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(),
270 completion_callback
));
273 if (error
!= FILE_ERROR_OK
) {
274 completion_callback
.Run(error
);
278 if (!entry
->file_info().is_directory()) {
279 completion_callback
.Run(FILE_ERROR_NOT_A_DIRECTORY
);
283 DirectoryFetchInfo
directory_fetch_info(
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)
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
,
309 DCHECK(thread_checker_
.CalledOnValidThread());
310 DCHECK(!completion_callback
.is_null());
312 if (error
!= FILE_ERROR_OK
) {
313 completion_callback
.Run(error
);
317 ResourceEntry
* entry
= new ResourceEntry
;
318 base::PostTaskAndReplyWithResult(
319 blocking_task_runner_
.get(),
321 base::Bind(&ResourceMetadata::GetResourceEntryByPath
,
322 base::Unretained(resource_metadata_
),
325 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry
,
326 weak_ptr_factory_
.GetWeakPtr(),
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
);
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(),
355 base::Bind(&CheckLocalState
,
361 base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState
,
362 weak_ptr_factory_
.GetWeakPtr(),
363 base::Passed(&about_resource
),
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
,
375 DCHECK(thread_checker_
.CalledOnValidThread());
376 DCHECK(about_resource
);
378 if (error
!= FILE_ERROR_OK
) {
379 OnDirectoryLoadComplete(local_id
, error
);
382 // This entry does not exist on the server.
383 if (entry
->resource_id().empty()) {
384 OnDirectoryLoadComplete(local_id
, FILE_ERROR_OK
);
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
);
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
,
413 DCHECK(thread_checker_
.CalledOnValidThread());
415 LoadCallbackMap::iterator it
= pending_load_callback_
.find(local_id
);
416 if (it
== pending_load_callback_
.end())
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
);
432 ResourceEntryVector
* entries
= new ResourceEntryVector
;
433 base::PostTaskAndReplyWithResult(
434 blocking_task_runner_
.get(),
436 base::Bind(&ResourceMetadata::ReadDirectoryById
,
437 base::Unretained(resource_metadata_
), local_id
, entries
),
438 base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead
,
439 weak_ptr_factory_
.GetWeakPtr(),
441 base::Owned(entries
)));
444 void DirectoryLoader::OnDirectoryLoadCompleteAfterRead(
445 const std::string
& local_id
,
446 const ResourceEntryVector
* entries
,
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())
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(),
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
);
507 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad
,
508 weak_ptr_factory_
.GetWeakPtr(),
509 directory_fetch_info
,
513 void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
514 const DirectoryFetchInfo
& directory_fetch_info
,
515 FeedFetcher
* fetcher
,
517 DCHECK(thread_checker_
.CalledOnValidThread());
518 DCHECK(!directory_fetch_info
.empty());
520 // Delete the fetcher.
521 fast_fetch_feed_fetcher_set_
.erase(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
);
537 // Update changestamp and get the directory path.
538 base::FilePath
* directory_path
= new base::FilePath
;
539 base::PostTaskAndReplyWithResult(
540 blocking_task_runner_
.get(),
542 base::Bind(&UpdateChangestamp
,
544 directory_fetch_info
,
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
,
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
,
566 OnDirectoryReloaded(*directory_path
));
570 } // namespace internal