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