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.
4
5 #include "chrome/browser/chromeos/drive/change_list_loader.h"
6
7 #include <set>
8
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"
22 #include "url/gurl.h"
23
24 using content::BrowserThread;
25
26 namespace drive {
27 namespace internal {
28
29 typedef base::Callback<void(FileError, ScopedVector<ChangeList>)>
30 FeedFetcherCallback;
31
32 class ChangeListLoader::FeedFetcher {
33 public:
~FeedFetcher()34 virtual ~FeedFetcher() {}
35 virtual void Run(const FeedFetcherCallback& callback) = 0;
36 };
37
38 namespace {
39
40 // Fetches all the (currently available) resource entries from the server.
41 class FullFeedFetcher : public ChangeListLoader::FeedFetcher {
42 public:
FullFeedFetcher(JobScheduler * scheduler)43 explicit FullFeedFetcher(JobScheduler* scheduler)
44 : scheduler_(scheduler),
45 weak_ptr_factory_(this) {
46 }
47
~FullFeedFetcher()48 virtual ~FullFeedFetcher() {
49 }
50
Run(const FeedFetcherCallback & callback)51 virtual void Run(const FeedFetcherCallback& callback) OVERRIDE {
52 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
53 DCHECK(!callback.is_null());
54
55 // Remember the time stamp for usage stats.
56 start_time_ = base::TimeTicks::Now();
57
58 // This is full resource list fetch.
59 scheduler_->GetAllFileList(
60 base::Bind(&FullFeedFetcher::OnFileListFetched,
61 weak_ptr_factory_.GetWeakPtr(), callback));
62 }
63
64 private:
OnFileListFetched(const FeedFetcherCallback & callback,google_apis::GDataErrorCode status,scoped_ptr<google_apis::FileList> file_list)65 void OnFileListFetched(const FeedFetcherCallback& callback,
66 google_apis::GDataErrorCode status,
67 scoped_ptr<google_apis::FileList> file_list) {
68 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
69 DCHECK(!callback.is_null());
70
71 FileError error = GDataToFileError(status);
72 if (error != FILE_ERROR_OK) {
73 callback.Run(error, ScopedVector<ChangeList>());
74 return;
75 }
76
77 DCHECK(file_list);
78 change_lists_.push_back(new ChangeList(*file_list));
79
80 if (!file_list->next_link().is_empty()) {
81 // There is the remaining result so fetch it.
82 scheduler_->GetRemainingFileList(
83 file_list->next_link(),
84 base::Bind(&FullFeedFetcher::OnFileListFetched,
85 weak_ptr_factory_.GetWeakPtr(), callback));
86 return;
87 }
88
89 UMA_HISTOGRAM_LONG_TIMES("Drive.FullFeedLoadTime",
90 base::TimeTicks::Now() - start_time_);
91
92 // Note: The fetcher is managed by ChangeListLoader, and the instance
93 // will be deleted in the callback. Do not touch the fields after this
94 // invocation.
95 callback.Run(FILE_ERROR_OK, change_lists_.Pass());
96 }
97
98 JobScheduler* scheduler_;
99 ScopedVector<ChangeList> change_lists_;
100 base::TimeTicks start_time_;
101 base::WeakPtrFactory<FullFeedFetcher> weak_ptr_factory_;
102 DISALLOW_COPY_AND_ASSIGN(FullFeedFetcher);
103 };
104
105 // Fetches the delta changes since |start_change_id|.
106 class DeltaFeedFetcher : public ChangeListLoader::FeedFetcher {
107 public:
DeltaFeedFetcher(JobScheduler * scheduler,int64 start_change_id)108 DeltaFeedFetcher(JobScheduler* scheduler, int64 start_change_id)
109 : scheduler_(scheduler),
110 start_change_id_(start_change_id),
111 weak_ptr_factory_(this) {
112 }
113
~DeltaFeedFetcher()114 virtual ~DeltaFeedFetcher() {
115 }
116
Run(const FeedFetcherCallback & callback)117 virtual void Run(const FeedFetcherCallback& callback) OVERRIDE {
118 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
119 DCHECK(!callback.is_null());
120
121 scheduler_->GetChangeList(
122 start_change_id_,
123 base::Bind(&DeltaFeedFetcher::OnChangeListFetched,
124 weak_ptr_factory_.GetWeakPtr(), callback));
125 }
126
127 private:
OnChangeListFetched(const FeedFetcherCallback & callback,google_apis::GDataErrorCode status,scoped_ptr<google_apis::ChangeList> change_list)128 void OnChangeListFetched(const FeedFetcherCallback& callback,
129 google_apis::GDataErrorCode status,
130 scoped_ptr<google_apis::ChangeList> change_list) {
131 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
132 DCHECK(!callback.is_null());
133
134 FileError error = GDataToFileError(status);
135 if (error != FILE_ERROR_OK) {
136 callback.Run(error, ScopedVector<ChangeList>());
137 return;
138 }
139
140 DCHECK(change_list);
141 change_lists_.push_back(new ChangeList(*change_list));
142
143 if (!change_list->next_link().is_empty()) {
144 // There is the remaining result so fetch it.
145 scheduler_->GetRemainingChangeList(
146 change_list->next_link(),
147 base::Bind(&DeltaFeedFetcher::OnChangeListFetched,
148 weak_ptr_factory_.GetWeakPtr(), callback));
149 return;
150 }
151
152 // Note: The fetcher is managed by ChangeListLoader, and the instance
153 // will be deleted in the callback. Do not touch the fields after this
154 // invocation.
155 callback.Run(FILE_ERROR_OK, change_lists_.Pass());
156 }
157
158 JobScheduler* scheduler_;
159 int64 start_change_id_;
160 ScopedVector<ChangeList> change_lists_;
161 base::WeakPtrFactory<DeltaFeedFetcher> weak_ptr_factory_;
162 DISALLOW_COPY_AND_ASSIGN(DeltaFeedFetcher);
163 };
164
165 } // namespace
166
LoaderController()167 LoaderController::LoaderController()
168 : lock_count_(0),
169 weak_ptr_factory_(this) {
170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171 }
172
~LoaderController()173 LoaderController::~LoaderController() {
174 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
175 }
176
GetLock()177 scoped_ptr<base::ScopedClosureRunner> LoaderController::GetLock() {
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179
180 ++lock_count_;
181 return make_scoped_ptr(new base::ScopedClosureRunner(
182 base::Bind(&LoaderController::Unlock,
183 weak_ptr_factory_.GetWeakPtr())));
184 }
185
ScheduleRun(const base::Closure & task)186 void LoaderController::ScheduleRun(const base::Closure& task) {
187 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
188 DCHECK(!task.is_null());
189
190 if (lock_count_ > 0) {
191 pending_tasks_.push_back(task);
192 } else {
193 task.Run();
194 }
195 }
196
Unlock()197 void LoaderController::Unlock() {
198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
199 DCHECK_LT(0, lock_count_);
200
201 if (--lock_count_ > 0)
202 return;
203
204 std::vector<base::Closure> tasks;
205 tasks.swap(pending_tasks_);
206 for (size_t i = 0; i < tasks.size(); ++i)
207 tasks[i].Run();
208 }
209
AboutResourceLoader(JobScheduler * scheduler)210 AboutResourceLoader::AboutResourceLoader(JobScheduler* scheduler)
211 : scheduler_(scheduler),
212 weak_ptr_factory_(this) {
213 }
214
~AboutResourceLoader()215 AboutResourceLoader::~AboutResourceLoader() {}
216
GetAboutResource(const google_apis::AboutResourceCallback & callback)217 void AboutResourceLoader::GetAboutResource(
218 const google_apis::AboutResourceCallback& callback) {
219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
220 DCHECK(!callback.is_null());
221
222 if (cached_about_resource_) {
223 base::MessageLoopProxy::current()->PostTask(
224 FROM_HERE,
225 base::Bind(
226 callback,
227 google_apis::HTTP_NO_CONTENT,
228 base::Passed(scoped_ptr<google_apis::AboutResource>(
229 new google_apis::AboutResource(*cached_about_resource_)))));
230 } else {
231 UpdateAboutResource(callback);
232 }
233 }
234
UpdateAboutResource(const google_apis::AboutResourceCallback & callback)235 void AboutResourceLoader::UpdateAboutResource(
236 const google_apis::AboutResourceCallback& callback) {
237 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
238 DCHECK(!callback.is_null());
239
240 scheduler_->GetAboutResource(
241 base::Bind(&AboutResourceLoader::UpdateAboutResourceAfterGetAbout,
242 weak_ptr_factory_.GetWeakPtr(),
243 callback));
244 }
245
UpdateAboutResourceAfterGetAbout(const google_apis::AboutResourceCallback & callback,google_apis::GDataErrorCode status,scoped_ptr<google_apis::AboutResource> about_resource)246 void AboutResourceLoader::UpdateAboutResourceAfterGetAbout(
247 const google_apis::AboutResourceCallback& callback,
248 google_apis::GDataErrorCode status,
249 scoped_ptr<google_apis::AboutResource> about_resource) {
250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
251 DCHECK(!callback.is_null());
252 FileError error = GDataToFileError(status);
253
254 if (error == FILE_ERROR_OK) {
255 if (cached_about_resource_ &&
256 cached_about_resource_->largest_change_id() >
257 about_resource->largest_change_id()) {
258 LOG(WARNING) << "Local cached about resource is fresher than server, "
259 << "local = " << cached_about_resource_->largest_change_id()
260 << ", server = " << about_resource->largest_change_id();
261 }
262
263 cached_about_resource_.reset(
264 new google_apis::AboutResource(*about_resource));
265 }
266
267 callback.Run(status, about_resource.Pass());
268 }
269
ChangeListLoader(EventLogger * logger,base::SequencedTaskRunner * blocking_task_runner,ResourceMetadata * resource_metadata,JobScheduler * scheduler,AboutResourceLoader * about_resource_loader,LoaderController * loader_controller)270 ChangeListLoader::ChangeListLoader(
271 EventLogger* logger,
272 base::SequencedTaskRunner* blocking_task_runner,
273 ResourceMetadata* resource_metadata,
274 JobScheduler* scheduler,
275 AboutResourceLoader* about_resource_loader,
276 LoaderController* loader_controller)
277 : logger_(logger),
278 blocking_task_runner_(blocking_task_runner),
279 resource_metadata_(resource_metadata),
280 scheduler_(scheduler),
281 about_resource_loader_(about_resource_loader),
282 loader_controller_(loader_controller),
283 loaded_(false),
284 weak_ptr_factory_(this) {
285 }
286
~ChangeListLoader()287 ChangeListLoader::~ChangeListLoader() {
288 }
289
IsRefreshing() const290 bool ChangeListLoader::IsRefreshing() const {
291 // Callback for change list loading is stored in pending_load_callback_.
292 // It is non-empty if and only if there is an in-flight loading operation.
293 return !pending_load_callback_.empty();
294 }
295
AddObserver(ChangeListLoaderObserver * observer)296 void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) {
297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298 observers_.AddObserver(observer);
299 }
300
RemoveObserver(ChangeListLoaderObserver * observer)301 void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
302 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
303 observers_.RemoveObserver(observer);
304 }
305
CheckForUpdates(const FileOperationCallback & callback)306 void ChangeListLoader::CheckForUpdates(const FileOperationCallback& callback) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308 DCHECK(!callback.is_null());
309
310 if (IsRefreshing()) {
311 // There is in-flight loading. So keep the callback here, and check for
312 // updates when the in-flight loading is completed.
313 pending_update_check_callback_ = callback;
314 return;
315 }
316
317 if (loaded_) {
318 // We only start to check for updates iff the load is done.
319 // I.e., we ignore checking updates if not loaded to avoid starting the
320 // load without user's explicit interaction (such as opening Drive).
321 logger_->Log(logging::LOG_INFO, "Checking for updates");
322 Load(callback);
323 }
324 }
325
LoadIfNeeded(const FileOperationCallback & callback)326 void ChangeListLoader::LoadIfNeeded(const FileOperationCallback& callback) {
327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
328 DCHECK(!callback.is_null());
329
330 // If the metadata is not yet loaded, start loading.
331 if (!loaded_)
332 Load(callback);
333 }
334
Load(const FileOperationCallback & callback)335 void ChangeListLoader::Load(const FileOperationCallback& callback) {
336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
337 DCHECK(!callback.is_null());
338
339 // Check if this is the first time this ChangeListLoader do loading.
340 // Note: IsRefreshing() depends on pending_load_callback_ so check in advance.
341 const bool is_initial_load = (!loaded_ && !IsRefreshing());
342
343 // Register the callback function to be called when it is loaded.
344 pending_load_callback_.push_back(callback);
345
346 // If loading task is already running, do nothing.
347 if (pending_load_callback_.size() > 1)
348 return;
349
350 // Check the current status of local metadata, and start loading if needed.
351 int64* local_changestamp = new int64(0);
352 base::PostTaskAndReplyWithResult(
353 blocking_task_runner_,
354 FROM_HERE,
355 base::Bind(&ResourceMetadata::GetLargestChangestamp,
356 base::Unretained(resource_metadata_),
357 local_changestamp),
358 base::Bind(&ChangeListLoader::LoadAfterGetLargestChangestamp,
359 weak_ptr_factory_.GetWeakPtr(),
360 is_initial_load,
361 base::Owned(local_changestamp)));
362 }
363
LoadAfterGetLargestChangestamp(bool is_initial_load,const int64 * local_changestamp,FileError error)364 void ChangeListLoader::LoadAfterGetLargestChangestamp(
365 bool is_initial_load,
366 const int64* local_changestamp,
367 FileError error) {
368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
369
370 if (error != FILE_ERROR_OK) {
371 OnChangeListLoadComplete(error);
372 return;
373 }
374
375 if (is_initial_load && *local_changestamp > 0) {
376 // The local data is usable. Flush callbacks to tell loading was successful.
377 OnChangeListLoadComplete(FILE_ERROR_OK);
378
379 // Continues to load from server in background.
380 // Put dummy callbacks to indicate that fetching is still continuing.
381 pending_load_callback_.push_back(
382 base::Bind(&util::EmptyFileOperationCallback));
383 }
384
385 about_resource_loader_->UpdateAboutResource(
386 base::Bind(&ChangeListLoader::LoadAfterGetAboutResource,
387 weak_ptr_factory_.GetWeakPtr(),
388 *local_changestamp));
389 }
390
LoadAfterGetAboutResource(int64 local_changestamp,google_apis::GDataErrorCode status,scoped_ptr<google_apis::AboutResource> about_resource)391 void ChangeListLoader::LoadAfterGetAboutResource(
392 int64 local_changestamp,
393 google_apis::GDataErrorCode status,
394 scoped_ptr<google_apis::AboutResource> about_resource) {
395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
396
397 FileError error = GDataToFileError(status);
398 if (error != FILE_ERROR_OK) {
399 OnChangeListLoadComplete(error);
400 return;
401 }
402
403 DCHECK(about_resource);
404
405 int64 remote_changestamp = about_resource->largest_change_id();
406 int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0;
407 if (local_changestamp >= remote_changestamp) {
408 if (local_changestamp > remote_changestamp) {
409 LOG(WARNING) << "Local resource metadata is fresher than server, "
410 << "local = " << local_changestamp
411 << ", server = " << remote_changestamp;
412 }
413
414 // No changes detected, tell the client that the loading was successful.
415 OnChangeListLoadComplete(FILE_ERROR_OK);
416 } else {
417 // Start loading the change list.
418 LoadChangeListFromServer(start_changestamp);
419 }
420 }
421
OnChangeListLoadComplete(FileError error)422 void ChangeListLoader::OnChangeListLoadComplete(FileError error) {
423 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
424
425 if (!loaded_ && error == FILE_ERROR_OK) {
426 loaded_ = true;
427 FOR_EACH_OBSERVER(ChangeListLoaderObserver,
428 observers_,
429 OnInitialLoadComplete());
430 }
431
432 for (size_t i = 0; i < pending_load_callback_.size(); ++i) {
433 base::MessageLoopProxy::current()->PostTask(
434 FROM_HERE,
435 base::Bind(pending_load_callback_[i], error));
436 }
437 pending_load_callback_.clear();
438
439 // If there is pending update check, try to load the change from the server
440 // again, because there may exist an update during the completed loading.
441 if (!pending_update_check_callback_.is_null()) {
442 Load(base::ResetAndReturn(&pending_update_check_callback_));
443 }
444 }
445
LoadChangeListFromServer(int64 start_changestamp)446 void ChangeListLoader::LoadChangeListFromServer(int64 start_changestamp) {
447 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
448 DCHECK(!change_feed_fetcher_);
449 DCHECK(about_resource_loader_->cached_about_resource());
450
451 bool is_delta_update = start_changestamp != 0;
452
453 // Set up feed fetcher.
454 if (is_delta_update) {
455 change_feed_fetcher_.reset(
456 new DeltaFeedFetcher(scheduler_, start_changestamp));
457 } else {
458 change_feed_fetcher_.reset(new FullFeedFetcher(scheduler_));
459 }
460
461 // Make a copy of cached_about_resource_ to remember at which changestamp we
462 // are fetching change list.
463 change_feed_fetcher_->Run(
464 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList,
465 weak_ptr_factory_.GetWeakPtr(),
466 base::Passed(make_scoped_ptr(new google_apis::AboutResource(
467 *about_resource_loader_->cached_about_resource()))),
468 is_delta_update));
469 }
470
LoadChangeListFromServerAfterLoadChangeList(scoped_ptr<google_apis::AboutResource> about_resource,bool is_delta_update,FileError error,ScopedVector<ChangeList> change_lists)471 void ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList(
472 scoped_ptr<google_apis::AboutResource> about_resource,
473 bool is_delta_update,
474 FileError error,
475 ScopedVector<ChangeList> change_lists) {
476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
477 DCHECK(about_resource);
478
479 // Delete the fetcher first.
480 change_feed_fetcher_.reset();
481
482 if (error != FILE_ERROR_OK) {
483 OnChangeListLoadComplete(error);
484 return;
485 }
486
487 ChangeListProcessor* change_list_processor =
488 new ChangeListProcessor(resource_metadata_);
489 // Don't send directory content change notification while performing
490 // the initial content retrieval.
491 const bool should_notify_changed_directories = is_delta_update;
492
493 logger_->Log(logging::LOG_INFO,
494 "Apply change lists (is delta: %d)",
495 is_delta_update);
496 loader_controller_->ScheduleRun(base::Bind(
497 base::IgnoreResult(
498 &base::PostTaskAndReplyWithResult<FileError, FileError>),
499 blocking_task_runner_,
500 FROM_HERE,
501 base::Bind(&ChangeListProcessor::Apply,
502 base::Unretained(change_list_processor),
503 base::Passed(&about_resource),
504 base::Passed(&change_lists),
505 is_delta_update),
506 base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterUpdate,
507 weak_ptr_factory_.GetWeakPtr(),
508 base::Owned(change_list_processor),
509 should_notify_changed_directories,
510 base::Time::Now())));
511 }
512
LoadChangeListFromServerAfterUpdate(ChangeListProcessor * change_list_processor,bool should_notify_changed_directories,const base::Time & start_time,FileError error)513 void ChangeListLoader::LoadChangeListFromServerAfterUpdate(
514 ChangeListProcessor* change_list_processor,
515 bool should_notify_changed_directories,
516 const base::Time& start_time,
517 FileError error) {
518 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
519
520 const base::TimeDelta elapsed = base::Time::Now() - start_time;
521 logger_->Log(logging::LOG_INFO,
522 "Change lists applied (elapsed time: %sms)",
523 base::Int64ToString(elapsed.InMilliseconds()).c_str());
524
525 if (should_notify_changed_directories) {
526 for (std::set<base::FilePath>::iterator dir_iter =
527 change_list_processor->changed_dirs().begin();
528 dir_iter != change_list_processor->changed_dirs().end();
529 ++dir_iter) {
530 FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
531 OnDirectoryChanged(*dir_iter));
532 }
533 }
534
535 OnChangeListLoadComplete(error);
536
537 FOR_EACH_OBSERVER(ChangeListLoaderObserver,
538 observers_,
539 OnLoadFromServerComplete());
540 }
541
542 } // namespace internal
543 } // namespace drive
544