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_CURRENTLY_ON(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::DriveApiErrorCode status
,
125 scoped_ptr
<google_apis::FileList
> file_list
) {
126 DCHECK_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(BrowserThread::UI
);
220 observers_
.AddObserver(observer
);
223 void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver
* observer
) {
224 DCHECK_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(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 about_resource_loader_
->GetAboutResource(
302 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource
,
303 weak_ptr_factory_
.GetWeakPtr(), local_id
));
306 void DirectoryLoader::ReadDirectoryAfterLoadParent(
307 const base::FilePath
& directory_path
,
308 const ReadDirectoryEntriesCallback
& entries_callback
,
309 const FileOperationCallback
& completion_callback
,
311 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
312 DCHECK(!completion_callback
.is_null());
314 if (error
!= FILE_ERROR_OK
) {
315 completion_callback
.Run(error
);
319 ResourceEntry
* entry
= new ResourceEntry
;
320 base::PostTaskAndReplyWithResult(
321 blocking_task_runner_
.get(),
323 base::Bind(&ResourceMetadata::GetResourceEntryByPath
,
324 base::Unretained(resource_metadata_
),
327 base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry
,
328 weak_ptr_factory_
.GetWeakPtr(),
332 false, // should_try_loading_parent
333 base::Owned(entry
)));
336 void DirectoryLoader::ReadDirectoryAfterGetAboutResource(
337 const std::string
& local_id
,
338 google_apis::DriveApiErrorCode status
,
339 scoped_ptr
<google_apis::AboutResource
> about_resource
) {
340 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
342 FileError error
= GDataToFileError(status
);
343 if (error
!= FILE_ERROR_OK
) {
344 OnDirectoryLoadComplete(local_id
, error
);
348 DCHECK(about_resource
);
350 // Check the current status of local metadata, and start loading if needed.
351 google_apis::AboutResource
* about_resource_ptr
= about_resource
.get();
352 ResourceEntry
* entry
= new ResourceEntry
;
353 int64
* local_changestamp
= new int64
;
354 base::PostTaskAndReplyWithResult(
355 blocking_task_runner_
.get(),
357 base::Bind(&CheckLocalState
,
363 base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState
,
364 weak_ptr_factory_
.GetWeakPtr(),
365 base::Passed(&about_resource
),
368 base::Owned(local_changestamp
)));
371 void DirectoryLoader::ReadDirectoryAfterCheckLocalState(
372 scoped_ptr
<google_apis::AboutResource
> about_resource
,
373 const std::string
& local_id
,
374 const ResourceEntry
* entry
,
375 const int64
* local_changestamp
,
377 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
378 DCHECK(about_resource
);
380 if (error
!= FILE_ERROR_OK
) {
381 OnDirectoryLoadComplete(local_id
, error
);
384 // This entry does not exist on the server.
385 if (entry
->resource_id().empty()) {
386 OnDirectoryLoadComplete(local_id
, FILE_ERROR_OK
);
390 int64 remote_changestamp
= about_resource
->largest_change_id();
392 // Start loading the directory.
393 int64 directory_changestamp
= std::max(
394 entry
->directory_specific_info().changestamp(), *local_changestamp
);
396 DirectoryFetchInfo
directory_fetch_info(
397 local_id
, entry
->resource_id(), remote_changestamp
);
399 // If the directory's changestamp is up-to-date or the global changestamp of
400 // the metadata DB is new enough (which means the normal changelist loading
401 // should finish very soon), just schedule to run the callback, as there is no
402 // need to fetch the directory.
403 if (directory_changestamp
>= remote_changestamp
||
404 *local_changestamp
+ kMinimumChangestampGap
> remote_changestamp
) {
405 OnDirectoryLoadComplete(local_id
, FILE_ERROR_OK
);
407 // Start fetching the directory content, and mark it with the changestamp
408 // |remote_changestamp|.
409 LoadDirectoryFromServer(directory_fetch_info
);
413 void DirectoryLoader::OnDirectoryLoadComplete(const std::string
& local_id
,
415 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
417 LoadCallbackMap::iterator it
= pending_load_callback_
.find(local_id
);
418 if (it
== pending_load_callback_
.end())
421 // No need to read metadata when no one needs entries.
422 bool needs_to_send_entries
= false;
423 for (size_t i
= 0; i
< it
->second
.size(); ++i
) {
424 const ReadDirectoryCallbackState
& callback_state
= it
->second
[i
];
425 if (!callback_state
.entries_callback
.is_null())
426 needs_to_send_entries
= true;
429 if (!needs_to_send_entries
) {
430 OnDirectoryLoadCompleteAfterRead(local_id
, NULL
, FILE_ERROR_OK
);
434 ResourceEntryVector
* entries
= new ResourceEntryVector
;
435 base::PostTaskAndReplyWithResult(
436 blocking_task_runner_
.get(),
438 base::Bind(&ResourceMetadata::ReadDirectoryById
,
439 base::Unretained(resource_metadata_
), local_id
, entries
),
440 base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead
,
441 weak_ptr_factory_
.GetWeakPtr(),
443 base::Owned(entries
)));
446 void DirectoryLoader::OnDirectoryLoadCompleteAfterRead(
447 const std::string
& local_id
,
448 const ResourceEntryVector
* entries
,
450 LoadCallbackMap::iterator it
= pending_load_callback_
.find(local_id
);
451 if (it
!= pending_load_callback_
.end()) {
452 DVLOG(1) << "Running callback for " << local_id
;
454 if (error
== FILE_ERROR_OK
&& entries
)
455 SendEntries(local_id
, *entries
);
457 for (size_t i
= 0; i
< it
->second
.size(); ++i
) {
458 const ReadDirectoryCallbackState
& callback_state
= it
->second
[i
];
459 callback_state
.completion_callback
.Run(error
);
461 pending_load_callback_
.erase(it
);
465 void DirectoryLoader::SendEntries(const std::string
& local_id
,
466 const ResourceEntryVector
& entries
) {
467 LoadCallbackMap::iterator it
= pending_load_callback_
.find(local_id
);
468 DCHECK(it
!= pending_load_callback_
.end());
470 for (size_t i
= 0; i
< it
->second
.size(); ++i
) {
471 ReadDirectoryCallbackState
* callback_state
= &it
->second
[i
];
472 if (callback_state
->entries_callback
.is_null())
475 // Filter out entries which were already sent.
476 scoped_ptr
<ResourceEntryVector
> entries_to_send(new ResourceEntryVector
);
477 for (size_t i
= 0; i
< entries
.size(); ++i
) {
478 const ResourceEntry
& entry
= entries
[i
];
479 if (!callback_state
->sent_entry_names
.count(entry
.base_name())) {
480 callback_state
->sent_entry_names
.insert(entry
.base_name());
481 entries_to_send
->push_back(entry
);
484 callback_state
->entries_callback
.Run(entries_to_send
.Pass());
488 void DirectoryLoader::LoadDirectoryFromServer(
489 const DirectoryFetchInfo
& directory_fetch_info
) {
490 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
491 DCHECK(!directory_fetch_info
.empty());
492 DVLOG(1) << "Start loading directory: " << directory_fetch_info
.ToString();
494 const google_apis::AboutResource
* about_resource
=
495 about_resource_loader_
->cached_about_resource();
496 DCHECK(about_resource
);
498 logger_
->Log(logging::LOG_INFO
,
499 "Fast-fetch start: %s; Server changestamp: %s",
500 directory_fetch_info
.ToString().c_str(),
502 about_resource
->largest_change_id()).c_str());
504 FeedFetcher
* fetcher
= new FeedFetcher(this,
505 directory_fetch_info
,
506 about_resource
->root_folder_id());
507 fast_fetch_feed_fetcher_set_
.insert(fetcher
);
509 base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad
,
510 weak_ptr_factory_
.GetWeakPtr(),
511 directory_fetch_info
,
515 void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
516 const DirectoryFetchInfo
& directory_fetch_info
,
517 FeedFetcher
* fetcher
,
519 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
520 DCHECK(!directory_fetch_info
.empty());
522 // Delete the fetcher.
523 fast_fetch_feed_fetcher_set_
.erase(fetcher
);
526 logger_
->Log(logging::LOG_INFO
,
527 "Fast-fetch complete: %s => %s",
528 directory_fetch_info
.ToString().c_str(),
529 FileErrorToString(error
).c_str());
531 if (error
!= FILE_ERROR_OK
) {
532 LOG(ERROR
) << "Failed to load directory: "
533 << directory_fetch_info
.local_id()
534 << ": " << FileErrorToString(error
);
535 OnDirectoryLoadComplete(directory_fetch_info
.local_id(), error
);
539 // Update changestamp and get the directory path.
540 base::FilePath
* directory_path
= new base::FilePath
;
541 base::PostTaskAndReplyWithResult(
542 blocking_task_runner_
.get(),
544 base::Bind(&UpdateChangestamp
,
546 directory_fetch_info
,
549 &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp
,
550 weak_ptr_factory_
.GetWeakPtr(),
551 directory_fetch_info
,
552 base::Owned(directory_path
)));
555 void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp(
556 const DirectoryFetchInfo
& directory_fetch_info
,
557 const base::FilePath
* directory_path
,
559 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
561 DVLOG(1) << "Directory loaded: " << directory_fetch_info
.ToString();
562 OnDirectoryLoadComplete(directory_fetch_info
.local_id(), error
);
564 // Also notify the observers.
565 if (error
== FILE_ERROR_OK
&& !directory_path
->empty()) {
566 FOR_EACH_OBSERVER(ChangeListLoaderObserver
,
568 OnDirectoryReloaded(*directory_path
));
572 } // namespace internal