1 // Copyright (c) 2012 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/change_list_loader.h"
9 #include "base/callback.h"
10 #include "base/callback_helpers.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/time/time.h"
14 #include "chrome/browser/chromeos/drive/change_list_loader_observer.h"
15 #include "chrome/browser/chromeos/drive/change_list_processor.h"
16 #include "chrome/browser/chromeos/drive/file_system_util.h"
17 #include "chrome/browser/chromeos/drive/job_scheduler.h"
18 #include "chrome/browser/chromeos/drive/resource_metadata.h"
19 #include "chrome/browser/drive/event_logger.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "google_apis/drive/drive_api_parser.h"
24 using content::BrowserThread
;
29 typedef base::Callback
<void(FileError
, ScopedVector
<ChangeList
>)>
32 class ChangeListLoader::FeedFetcher
{
34 virtual ~FeedFetcher() {}
35 virtual void Run(const FeedFetcherCallback
& callback
) = 0;
40 // Fetches all the (currently available) resource entries from the server.
41 class FullFeedFetcher
: public ChangeListLoader::FeedFetcher
{
43 explicit FullFeedFetcher(JobScheduler
* scheduler
)
44 : scheduler_(scheduler
),
45 weak_ptr_factory_(this) {
48 ~FullFeedFetcher() override
{}
50 void Run(const FeedFetcherCallback
& callback
) override
{
51 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
52 DCHECK(!callback
.is_null());
54 // Remember the time stamp for usage stats.
55 start_time_
= base::TimeTicks::Now();
57 // This is full resource list fetch.
58 scheduler_
->GetAllFileList(
59 base::Bind(&FullFeedFetcher::OnFileListFetched
,
60 weak_ptr_factory_
.GetWeakPtr(), callback
));
64 void OnFileListFetched(const FeedFetcherCallback
& callback
,
65 google_apis::DriveApiErrorCode status
,
66 scoped_ptr
<google_apis::FileList
> file_list
) {
67 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
68 DCHECK(!callback
.is_null());
70 FileError error
= GDataToFileError(status
);
71 if (error
!= FILE_ERROR_OK
) {
72 callback
.Run(error
, ScopedVector
<ChangeList
>());
77 change_lists_
.push_back(new ChangeList(*file_list
));
79 if (!file_list
->next_link().is_empty()) {
80 // There is the remaining result so fetch it.
81 scheduler_
->GetRemainingFileList(
82 file_list
->next_link(),
83 base::Bind(&FullFeedFetcher::OnFileListFetched
,
84 weak_ptr_factory_
.GetWeakPtr(), callback
));
88 UMA_HISTOGRAM_LONG_TIMES("Drive.FullFeedLoadTime",
89 base::TimeTicks::Now() - start_time_
);
91 // Note: The fetcher is managed by ChangeListLoader, and the instance
92 // will be deleted in the callback. Do not touch the fields after this
94 callback
.Run(FILE_ERROR_OK
, change_lists_
.Pass());
97 JobScheduler
* scheduler_
;
98 ScopedVector
<ChangeList
> change_lists_
;
99 base::TimeTicks start_time_
;
100 base::WeakPtrFactory
<FullFeedFetcher
> weak_ptr_factory_
;
101 DISALLOW_COPY_AND_ASSIGN(FullFeedFetcher
);
104 // Fetches the delta changes since |start_change_id|.
105 class DeltaFeedFetcher
: public ChangeListLoader::FeedFetcher
{
107 DeltaFeedFetcher(JobScheduler
* scheduler
, int64 start_change_id
)
108 : scheduler_(scheduler
),
109 start_change_id_(start_change_id
),
110 weak_ptr_factory_(this) {
113 ~DeltaFeedFetcher() override
{}
115 void Run(const FeedFetcherCallback
& callback
) override
{
116 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
117 DCHECK(!callback
.is_null());
119 scheduler_
->GetChangeList(
121 base::Bind(&DeltaFeedFetcher::OnChangeListFetched
,
122 weak_ptr_factory_
.GetWeakPtr(), callback
));
126 void OnChangeListFetched(const FeedFetcherCallback
& callback
,
127 google_apis::DriveApiErrorCode status
,
128 scoped_ptr
<google_apis::ChangeList
> change_list
) {
129 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
130 DCHECK(!callback
.is_null());
132 FileError error
= GDataToFileError(status
);
133 if (error
!= FILE_ERROR_OK
) {
134 callback
.Run(error
, ScopedVector
<ChangeList
>());
139 change_lists_
.push_back(new ChangeList(*change_list
));
141 if (!change_list
->next_link().is_empty()) {
142 // There is the remaining result so fetch it.
143 scheduler_
->GetRemainingChangeList(
144 change_list
->next_link(),
145 base::Bind(&DeltaFeedFetcher::OnChangeListFetched
,
146 weak_ptr_factory_
.GetWeakPtr(), callback
));
150 // Note: The fetcher is managed by ChangeListLoader, and the instance
151 // will be deleted in the callback. Do not touch the fields after this
153 callback
.Run(FILE_ERROR_OK
, change_lists_
.Pass());
156 JobScheduler
* scheduler_
;
157 int64 start_change_id_
;
158 ScopedVector
<ChangeList
> change_lists_
;
159 base::WeakPtrFactory
<DeltaFeedFetcher
> weak_ptr_factory_
;
160 DISALLOW_COPY_AND_ASSIGN(DeltaFeedFetcher
);
165 LoaderController::LoaderController()
167 weak_ptr_factory_(this) {
168 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
171 LoaderController::~LoaderController() {
172 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
175 scoped_ptr
<base::ScopedClosureRunner
> LoaderController::GetLock() {
176 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
179 return make_scoped_ptr(new base::ScopedClosureRunner(
180 base::Bind(&LoaderController::Unlock
,
181 weak_ptr_factory_
.GetWeakPtr())));
184 void LoaderController::ScheduleRun(const base::Closure
& task
) {
185 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
186 DCHECK(!task
.is_null());
188 if (lock_count_
> 0) {
189 pending_tasks_
.push_back(task
);
195 void LoaderController::Unlock() {
196 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
197 DCHECK_LT(0, lock_count_
);
199 if (--lock_count_
> 0)
202 std::vector
<base::Closure
> tasks
;
203 tasks
.swap(pending_tasks_
);
204 for (size_t i
= 0; i
< tasks
.size(); ++i
)
208 AboutResourceLoader::AboutResourceLoader(JobScheduler
* scheduler
)
209 : scheduler_(scheduler
),
210 current_update_task_id_(-1),
211 weak_ptr_factory_(this) {
214 AboutResourceLoader::~AboutResourceLoader() {}
216 void AboutResourceLoader::GetAboutResource(
217 const google_apis::AboutResourceCallback
& callback
) {
218 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
219 DCHECK(!callback
.is_null());
221 // If the latest UpdateAboutResource task is still running. Wait for it,
222 if (pending_callbacks_
.count(current_update_task_id_
)) {
223 pending_callbacks_
[current_update_task_id_
].push_back(callback
);
227 if (cached_about_resource_
) {
228 base::MessageLoopProxy::current()->PostTask(
232 google_apis::HTTP_NO_CONTENT
,
233 base::Passed(scoped_ptr
<google_apis::AboutResource
>(
234 new google_apis::AboutResource(*cached_about_resource_
)))));
236 UpdateAboutResource(callback
);
240 void AboutResourceLoader::UpdateAboutResource(
241 const google_apis::AboutResourceCallback
& callback
) {
242 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
243 DCHECK(!callback
.is_null());
245 ++current_update_task_id_
;
246 pending_callbacks_
[current_update_task_id_
].push_back(callback
);
248 scheduler_
->GetAboutResource(
249 base::Bind(&AboutResourceLoader::UpdateAboutResourceAfterGetAbout
,
250 weak_ptr_factory_
.GetWeakPtr(),
251 current_update_task_id_
));
254 void AboutResourceLoader::UpdateAboutResourceAfterGetAbout(
256 google_apis::DriveApiErrorCode status
,
257 scoped_ptr
<google_apis::AboutResource
> about_resource
) {
258 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
259 FileError error
= GDataToFileError(status
);
261 const std::vector
<google_apis::AboutResourceCallback
> callbacks
=
262 pending_callbacks_
[task_id
];
263 pending_callbacks_
.erase(task_id
);
265 if (error
!= FILE_ERROR_OK
) {
266 for (size_t i
= 0; i
< callbacks
.size(); ++i
)
267 callbacks
[i
].Run(status
, scoped_ptr
<google_apis::AboutResource
>());
271 // Updates the cache when the resource is successfully obtained.
272 if (cached_about_resource_
&&
273 cached_about_resource_
->largest_change_id() >
274 about_resource
->largest_change_id()) {
275 LOG(WARNING
) << "Local cached about resource is fresher than server, "
276 << "local = " << cached_about_resource_
->largest_change_id()
277 << ", server = " << about_resource
->largest_change_id();
279 cached_about_resource_
.reset(new google_apis::AboutResource(*about_resource
));
281 for (size_t i
= 0; i
< callbacks
.size(); ++i
) {
284 make_scoped_ptr(new google_apis::AboutResource(*about_resource
)));
288 ChangeListLoader::ChangeListLoader(
290 base::SequencedTaskRunner
* blocking_task_runner
,
291 ResourceMetadata
* resource_metadata
,
292 JobScheduler
* scheduler
,
293 AboutResourceLoader
* about_resource_loader
,
294 LoaderController
* loader_controller
)
296 blocking_task_runner_(blocking_task_runner
),
297 resource_metadata_(resource_metadata
),
298 scheduler_(scheduler
),
299 about_resource_loader_(about_resource_loader
),
300 loader_controller_(loader_controller
),
302 weak_ptr_factory_(this) {
305 ChangeListLoader::~ChangeListLoader() {
308 bool ChangeListLoader::IsRefreshing() const {
309 // Callback for change list loading is stored in pending_load_callback_.
310 // It is non-empty if and only if there is an in-flight loading operation.
311 return !pending_load_callback_
.empty();
314 void ChangeListLoader::AddObserver(ChangeListLoaderObserver
* observer
) {
315 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
316 observers_
.AddObserver(observer
);
319 void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver
* observer
) {
320 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
321 observers_
.RemoveObserver(observer
);
324 void ChangeListLoader::CheckForUpdates(const FileOperationCallback
& callback
) {
325 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
326 DCHECK(!callback
.is_null());
328 // We only start to check for updates iff the load is done.
329 // I.e., we ignore checking updates if not loaded to avoid starting the
330 // load without user's explicit interaction (such as opening Drive).
331 if (!loaded_
&& !IsRefreshing())
334 // For each CheckForUpdates() request, always refresh the changestamp info.
335 about_resource_loader_
->UpdateAboutResource(
336 base::Bind(&ChangeListLoader::OnAboutResourceUpdated
,
337 weak_ptr_factory_
.GetWeakPtr()));
339 if (IsRefreshing()) {
340 // There is in-flight loading. So keep the callback here, and check for
341 // updates when the in-flight loading is completed.
342 pending_update_check_callback_
= callback
;
347 logger_
->Log(logging::LOG_INFO
, "Checking for updates");
351 void ChangeListLoader::LoadIfNeeded(const FileOperationCallback
& callback
) {
352 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
353 DCHECK(!callback
.is_null());
355 // If the metadata is not yet loaded, start loading.
356 if (!loaded_
&& !IsRefreshing())
360 void ChangeListLoader::Load(const FileOperationCallback
& callback
) {
361 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
362 DCHECK(!callback
.is_null());
364 // Check if this is the first time this ChangeListLoader do loading.
365 // Note: IsRefreshing() depends on pending_load_callback_ so check in advance.
366 const bool is_initial_load
= (!loaded_
&& !IsRefreshing());
368 // Register the callback function to be called when it is loaded.
369 pending_load_callback_
.push_back(callback
);
371 // If loading task is already running, do nothing.
372 if (pending_load_callback_
.size() > 1)
375 // Check the current status of local metadata, and start loading if needed.
376 int64
* local_changestamp
= new int64(0);
377 base::PostTaskAndReplyWithResult(
378 blocking_task_runner_
.get(),
380 base::Bind(&ResourceMetadata::GetLargestChangestamp
,
381 base::Unretained(resource_metadata_
),
383 base::Bind(&ChangeListLoader::LoadAfterGetLargestChangestamp
,
384 weak_ptr_factory_
.GetWeakPtr(),
386 base::Owned(local_changestamp
)));
389 void ChangeListLoader::LoadAfterGetLargestChangestamp(
390 bool is_initial_load
,
391 const int64
* local_changestamp
,
393 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
395 if (error
!= FILE_ERROR_OK
) {
396 OnChangeListLoadComplete(error
);
400 if (is_initial_load
&& *local_changestamp
> 0) {
401 // The local data is usable. Flush callbacks to tell loading was successful.
402 OnChangeListLoadComplete(FILE_ERROR_OK
);
404 // Continues to load from server in background.
405 // Put dummy callbacks to indicate that fetching is still continuing.
406 pending_load_callback_
.push_back(
407 base::Bind(&util::EmptyFileOperationCallback
));
410 about_resource_loader_
->GetAboutResource(
411 base::Bind(&ChangeListLoader::LoadAfterGetAboutResource
,
412 weak_ptr_factory_
.GetWeakPtr(),
413 *local_changestamp
));
416 void ChangeListLoader::LoadAfterGetAboutResource(
417 int64 local_changestamp
,
418 google_apis::DriveApiErrorCode status
,
419 scoped_ptr
<google_apis::AboutResource
> about_resource
) {
420 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
422 FileError error
= GDataToFileError(status
);
423 if (error
!= FILE_ERROR_OK
) {
424 OnChangeListLoadComplete(error
);
428 DCHECK(about_resource
);
430 int64 remote_changestamp
= about_resource
->largest_change_id();
431 int64 start_changestamp
= local_changestamp
> 0 ? local_changestamp
+ 1 : 0;
432 if (local_changestamp
>= remote_changestamp
) {
433 if (local_changestamp
> remote_changestamp
) {
434 LOG(WARNING
) << "Local resource metadata is fresher than server, "
435 << "local = " << local_changestamp
436 << ", server = " << remote_changestamp
;
439 // No changes detected, tell the client that the loading was successful.
440 OnChangeListLoadComplete(FILE_ERROR_OK
);
442 // Start loading the change list.
443 LoadChangeListFromServer(start_changestamp
);
447 void ChangeListLoader::OnChangeListLoadComplete(FileError error
) {
448 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
450 if (!loaded_
&& error
== FILE_ERROR_OK
) {
452 FOR_EACH_OBSERVER(ChangeListLoaderObserver
,
454 OnInitialLoadComplete());
457 for (size_t i
= 0; i
< pending_load_callback_
.size(); ++i
) {
458 base::MessageLoopProxy::current()->PostTask(
460 base::Bind(pending_load_callback_
[i
], error
));
462 pending_load_callback_
.clear();
464 // If there is pending update check, try to load the change from the server
465 // again, because there may exist an update during the completed loading.
466 if (!pending_update_check_callback_
.is_null()) {
467 Load(base::ResetAndReturn(&pending_update_check_callback_
));
471 void ChangeListLoader::OnAboutResourceUpdated(
472 google_apis::DriveApiErrorCode error
,
473 scoped_ptr
<google_apis::AboutResource
> resource
) {
474 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
476 if (drive::GDataToFileError(error
) != drive::FILE_ERROR_OK
) {
477 logger_
->Log(logging::LOG_ERROR
,
478 "Failed to update the about resource: %s",
479 google_apis::DriveApiErrorCodeToString(error
).c_str());
482 logger_
->Log(logging::LOG_INFO
,
483 "About resource updated to: %s",
484 base::Int64ToString(resource
->largest_change_id()).c_str());
487 void ChangeListLoader::LoadChangeListFromServer(int64 start_changestamp
) {
488 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
489 DCHECK(!change_feed_fetcher_
);
490 DCHECK(about_resource_loader_
->cached_about_resource());
492 bool is_delta_update
= start_changestamp
!= 0;
494 // Set up feed fetcher.
495 if (is_delta_update
) {
496 change_feed_fetcher_
.reset(
497 new DeltaFeedFetcher(scheduler_
, start_changestamp
));
499 change_feed_fetcher_
.reset(new FullFeedFetcher(scheduler_
));
502 // Make a copy of cached_about_resource_ to remember at which changestamp we
503 // are fetching change list.
504 change_feed_fetcher_
->Run(
505 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList
,
506 weak_ptr_factory_
.GetWeakPtr(),
507 base::Passed(make_scoped_ptr(new google_apis::AboutResource(
508 *about_resource_loader_
->cached_about_resource()))),
512 void ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList(
513 scoped_ptr
<google_apis::AboutResource
> about_resource
,
514 bool is_delta_update
,
516 ScopedVector
<ChangeList
> change_lists
) {
517 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
518 DCHECK(about_resource
);
520 // Delete the fetcher first.
521 change_feed_fetcher_
.reset();
523 if (error
!= FILE_ERROR_OK
) {
524 OnChangeListLoadComplete(error
);
528 ChangeListProcessor
* change_list_processor
=
529 new ChangeListProcessor(resource_metadata_
);
530 // Don't send directory content change notification while performing
531 // the initial content retrieval.
532 const bool should_notify_changed_directories
= is_delta_update
;
534 logger_
->Log(logging::LOG_INFO
,
535 "Apply change lists (is delta: %d)",
537 loader_controller_
->ScheduleRun(base::Bind(
539 &base::PostTaskAndReplyWithResult
<FileError
, FileError
>),
540 blocking_task_runner_
,
542 base::Bind(&ChangeListProcessor::Apply
,
543 base::Unretained(change_list_processor
),
544 base::Passed(&about_resource
),
545 base::Passed(&change_lists
),
547 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterUpdate
,
548 weak_ptr_factory_
.GetWeakPtr(),
549 base::Owned(change_list_processor
),
550 should_notify_changed_directories
,
551 base::Time::Now())));
554 void ChangeListLoader::LoadChangeListFromServerAfterUpdate(
555 ChangeListProcessor
* change_list_processor
,
556 bool should_notify_changed_directories
,
557 const base::Time
& start_time
,
559 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
561 const base::TimeDelta elapsed
= base::Time::Now() - start_time
;
562 logger_
->Log(logging::LOG_INFO
,
563 "Change lists applied (elapsed time: %sms)",
564 base::Int64ToString(elapsed
.InMilliseconds()).c_str());
566 if (should_notify_changed_directories
) {
567 FOR_EACH_OBSERVER(ChangeListLoaderObserver
,
569 OnFileChanged(change_list_processor
->changed_files()));
572 OnChangeListLoadComplete(error
);
574 FOR_EACH_OBSERVER(ChangeListLoaderObserver
,
576 OnLoadFromServerComplete());
579 } // namespace internal