change_list_loader.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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
24using content::BrowserThread;
25
26namespace drive {
27namespace internal {
28
29typedef base::Callback<void(FileError, ScopedVector<ChangeList>)>
30    FeedFetcherCallback;
31
32class ChangeListLoader::FeedFetcher {
33 public:
34  virtual ~FeedFetcher() {}
35  virtual void Run(const FeedFetcherCallback& callback) = 0;
36};
37
38namespace {
39
40// Fetches all the (currently available) resource entries from the server.
41class FullFeedFetcher : public ChangeListLoader::FeedFetcher {
42 public:
43  explicit FullFeedFetcher(JobScheduler* scheduler)
44      : scheduler_(scheduler),
45        weak_ptr_factory_(this) {
46  }
47
48  virtual ~FullFeedFetcher() {
49  }
50
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:
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|.
106class DeltaFeedFetcher : public ChangeListLoader::FeedFetcher {
107 public:
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
114  virtual ~DeltaFeedFetcher() {
115  }
116
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:
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
167LoaderController::LoaderController()
168    : lock_count_(0),
169      weak_ptr_factory_(this) {
170  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171}
172
173LoaderController::~LoaderController() {
174  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
175}
176
177scoped_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
186void 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
197void 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
210AboutResourceLoader::AboutResourceLoader(JobScheduler* scheduler)
211    : scheduler_(scheduler),
212      weak_ptr_factory_(this) {
213}
214
215AboutResourceLoader::~AboutResourceLoader() {}
216
217void 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
235void 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
246void 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
270ChangeListLoader::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
287ChangeListLoader::~ChangeListLoader() {
288}
289
290bool 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
296void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) {
297  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298  observers_.AddObserver(observer);
299}
300
301void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
302  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
303  observers_.RemoveObserver(observer);
304}
305
306void 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
326void 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
335void 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
364void 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
391void 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
422void 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
446void 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
471void 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
513void 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_EACH_OBSERVER(ChangeListLoaderObserver,
527                      observers_,
528                      OnFileChanged(change_list_processor->changed_files()));
529  }
530
531  OnChangeListLoadComplete(error);
532
533  FOR_EACH_OBSERVER(ChangeListLoaderObserver,
534                    observers_,
535                    OnLoadFromServerComplete());
536}
537
538}  // namespace internal
539}  // namespace drive
540