change_list_loader.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/metrics/histogram.h"
11#include "chrome/browser/chromeos/drive/change_list_loader_observer.h"
12#include "chrome/browser/chromeos/drive/change_list_processor.h"
13#include "chrome/browser/chromeos/drive/drive_file_system_util.h"
14#include "chrome/browser/chromeos/drive/drive_scheduler.h"
15#include "chrome/browser/chromeos/drive/drive_webapps_registry.h"
16#include "chrome/browser/google_apis/drive_api_parser.h"
17#include "chrome/browser/google_apis/drive_api_util.h"
18#include "content/public/browser/browser_thread.h"
19#include "googleurl/src/gurl.h"
20
21using content::BrowserThread;
22
23namespace drive {
24
25namespace {
26
27// Update the fetch progress UI per every this number of feeds.
28const int kFetchUiUpdateStep = 10;
29
30}  // namespace
31
32// Set of parameters sent to LoadFromServer.
33//
34// Value of |start_changestamp| determines the type of feed to load - 0 means
35// root feed, every other value would trigger delta feed.
36//
37// Loaded feed may be partial due to size limit on a single feed. In that case,
38// the loaded feed will have next feed url set. Iff |load_subsequent_feeds|
39// parameter is set, feed loader will load all subsequent feeds.
40//
41// If invoked as a part of content search, query will be set in |search_query|.
42// If |feed_to_load| is set, this is feed url that will be used to load feed.
43//
44// When all feeds are loaded, |feed_load_callback| is invoked with the retrieved
45// feeds. |feed_load_callback| must not be null.
46struct ChangeListLoader::LoadFeedParams {
47  LoadFeedParams()
48      : start_changestamp(0),
49        shared_with_me(false),
50        load_subsequent_feeds(true) {}
51
52  // Changestamps are positive numbers in increasing order. The difference
53  // between two changestamps is proportional equal to number of items in
54  // delta feed between them - bigger the difference, more likely bigger
55  // number of items in delta feeds.
56  int64 start_changestamp;
57  std::string search_query;
58  bool shared_with_me;
59  std::string directory_resource_id;
60  GURL feed_to_load;
61  bool load_subsequent_feeds;
62  ScopedVector<google_apis::ResourceList> feed_list;
63  scoped_ptr<GetResourceListUiState> ui_state;
64};
65
66// Defines set of parameters sent to callback OnNotifyResourceListFetched().
67// This is a trick to update the number of fetched documents frequently on
68// UI. Due to performance reason, we need to fetch a number of files at
69// a time. However, it'll take long time, and a user has no way to know
70// the current update state. In order to make users comfortable,
71// we increment the number of fetched documents with more frequent but smaller
72// steps than actual fetching.
73struct ChangeListLoader::GetResourceListUiState {
74  explicit GetResourceListUiState(base::TimeTicks start_time)
75      : num_fetched_documents(0),
76        num_showing_documents(0),
77        start_time(start_time),
78        ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory(this)) {
79  }
80
81  // The number of fetched documents.
82  int num_fetched_documents;
83
84  // The number documents shown on UI.
85  int num_showing_documents;
86
87  // When the UI update has started.
88  base::TimeTicks start_time;
89
90  // Time elapsed since the feed fetching was started.
91  base::TimeDelta feed_fetching_elapsed_time;
92
93  base::WeakPtrFactory<GetResourceListUiState> weak_ptr_factory;
94};
95
96ChangeListLoader::ChangeListLoader(DriveResourceMetadata* resource_metadata,
97                                   DriveScheduler* scheduler,
98                                   DriveWebAppsRegistry* webapps_registry)
99    : resource_metadata_(resource_metadata),
100      scheduler_(scheduler),
101      webapps_registry_(webapps_registry),
102      refreshing_(false),
103      last_known_remote_changestamp_(0),
104      loaded_(false),
105      ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
106}
107
108ChangeListLoader::~ChangeListLoader() {
109}
110
111void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) {
112  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
113  observers_.AddObserver(observer);
114}
115
116void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
117  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
118  observers_.RemoveObserver(observer);
119}
120
121void ChangeListLoader::LoadFromServerIfNeeded(
122    const DirectoryFetchInfo& directory_fetch_info,
123    const FileOperationCallback& callback) {
124  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
125  DCHECK(!callback.is_null());
126
127  // Sets the refreshing flag, so that the caller does not send refresh requests
128  // in parallel (see DriveFileSystem::CheckForUpdates). Corresponding
129  // "refresh_ = false" is in OnGetAboutResource when the cached feed is up to
130  // date, or in OnFeedFromServerLoaded called back from LoadFromServer().
131  refreshing_ = true;
132
133  // Drive v2 needs a separate application list fetch operation.
134  // On GData WAPI, it is not necessary in theory, because the response
135  // of account metadata can include both about account information (such as
136  // quota) and an application list at once.
137  // However, for Drive API v2 migration, we connect to the server twice
138  // (one for about account information and another for an application list)
139  // regardless of underlying API, so that we can simplify the code.
140  // Note that the size of account metadata on GData WAPI seems small enough
141  // and (by controlling the query parameter) the response for GetAboutResource
142  // operation doesn't contain application list. Thus, the effect should be
143  // small cost.
144  // TODO(haruki): Application list rarely changes and is not necessarily
145  // refreshed as often as files.
146  scheduler_->GetAppList(
147      base::Bind(&ChangeListLoader::OnGetAppList,
148                 weak_ptr_factory_.GetWeakPtr()));
149
150  // First fetch the latest changestamp to see if there were any new changes
151  // there at all.
152  scheduler_->GetAboutResource(
153      base::Bind(&ChangeListLoader::LoadFromServerIfNeededAfterGetAbout,
154                 weak_ptr_factory_.GetWeakPtr(),
155                 directory_fetch_info,
156                 callback));
157}
158
159void ChangeListLoader::LoadFromServerIfNeededAfterGetAbout(
160    const DirectoryFetchInfo& directory_fetch_info,
161    const FileOperationCallback& callback,
162    google_apis::GDataErrorCode status,
163    scoped_ptr<google_apis::AboutResource> about_resource) {
164  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
165  DCHECK(!callback.is_null());
166  DCHECK(refreshing_);
167  DCHECK_EQ(util::GDataToDriveFileError(status) == DRIVE_FILE_OK,
168            about_resource.get() != NULL);
169
170  if (util::GDataToDriveFileError(status) == DRIVE_FILE_OK) {
171    DCHECK(about_resource);
172    last_known_remote_changestamp_ = about_resource->largest_change_id();
173  }
174
175  resource_metadata_->GetLargestChangestamp(
176      base::Bind(&ChangeListLoader::CompareChangestampsAndLoadIfNeeded,
177                 weak_ptr_factory_.GetWeakPtr(),
178                 directory_fetch_info,
179                 callback,
180                 base::Passed(&about_resource)));
181}
182
183void ChangeListLoader::CompareChangestampsAndLoadIfNeeded(
184    const DirectoryFetchInfo& directory_fetch_info,
185    const FileOperationCallback& callback,
186    scoped_ptr<google_apis::AboutResource> about_resource,
187    int64 local_changestamp) {
188  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
189  DCHECK(!callback.is_null());
190  DCHECK(refreshing_);
191
192  int64 remote_changestamp =
193      about_resource ? about_resource->largest_change_id() : 0;
194  if (remote_changestamp > 0 && local_changestamp >= remote_changestamp) {
195    if (local_changestamp > remote_changestamp) {
196      LOG(WARNING) << "Cached client feed is fresher than server, client = "
197                   << local_changestamp
198                   << ", server = "
199                   << remote_changestamp;
200    }
201
202    // No changes detected, tell the client that the loading was successful.
203    OnChangeListLoadComplete(callback, DRIVE_FILE_OK);
204    return;
205  }
206
207  int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0;
208  if (start_changestamp == 0 && !about_resource.get()) {
209    // Full update needs AboutResource. If this is a full update, we should just
210    // give up. Note that to exit from the feed loading, we always have to flush
211    // the pending callback tasks via OnChangeListLoadComplete.
212    OnChangeListLoadComplete(callback, DRIVE_FILE_ERROR_FAILED);
213    return;
214  }
215
216  if (directory_fetch_info.empty()) {
217    // If the caller is not interested in a particular directory, just start
218    // loading the change list.
219    LoadChangeListFromServer(about_resource.Pass(),
220                             start_changestamp,
221                             callback);
222  } else if (directory_fetch_info.changestamp() < remote_changestamp) {
223    // If the caller is interested in a particular directory, and the
224    // directory changestamp is older than server's, start loading the
225    // directory first.
226    DVLOG(1) << "Fast-fetching directory: " << directory_fetch_info.ToString()
227             << "; remote_changestamp: " << remote_changestamp;
228    const DirectoryFetchInfo new_directory_fetch_info(
229        directory_fetch_info.resource_id(), remote_changestamp);
230    DoLoadDirectoryFromServer(
231        new_directory_fetch_info,
232        base::Bind(&ChangeListLoader::StartLoadChangeListFromServer,
233                   weak_ptr_factory_.GetWeakPtr(),
234                   directory_fetch_info,
235                   base::Passed(&about_resource),
236                   start_changestamp,
237                   callback));
238  } else {
239    // The directory is up-to-date, but not the case for other parts.
240    // Proceed to change list loading. StartLoadChangeListFromServer will
241    // run |callback| for notifying the directory is ready before feed load.
242    StartLoadChangeListFromServer(directory_fetch_info,
243                                  about_resource.Pass(),
244                                  start_changestamp,
245                                  callback,
246                                  DRIVE_FILE_OK);
247  }
248}
249
250void ChangeListLoader::LoadChangeListFromServer(
251    scoped_ptr<google_apis::AboutResource> about_resource,
252    int64 start_changestamp,
253    const FileOperationCallback& callback) {
254  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
255  DCHECK(!callback.is_null());
256  DCHECK(refreshing_);
257
258  scoped_ptr<LoadFeedParams> load_params(new LoadFeedParams);
259  load_params->start_changestamp = start_changestamp;
260  LoadFromServer(
261      load_params.Pass(),
262      base::Bind(&ChangeListLoader::UpdateMetadataFromFeedAfterLoadFromServer,
263                 weak_ptr_factory_.GetWeakPtr(),
264                 base::Passed(&about_resource),
265                 start_changestamp != 0,  // is_delta_feed
266                 callback));
267}
268
269void ChangeListLoader::StartLoadChangeListFromServer(
270    const DirectoryFetchInfo& directory_fetch_info,
271    scoped_ptr<google_apis::AboutResource> about_resource,
272    int64 start_changestamp,
273    const FileOperationCallback& callback,
274    DriveFileError error) {
275  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
276  DCHECK(!callback.is_null());
277  DCHECK(refreshing_);
278
279  if (error == DRIVE_FILE_OK) {
280    OnDirectoryLoadComplete(directory_fetch_info, callback, DRIVE_FILE_OK);
281    DVLOG(1) << "Fast-fetch was successful: " << directory_fetch_info.ToString()
282             << "; Start loading the change list";
283    // Stop passing |callback| as it's just consumed.
284    LoadChangeListFromServer(
285        about_resource.Pass(),
286        start_changestamp,
287        base::Bind(&util::EmptyFileOperationCallback));
288  } else {
289    // The directory fast-fetch failed, but the change list loading may
290    // succeed. Keep passing |callback| so it's run after the change list
291    // loading is complete.
292    LoadChangeListFromServer(
293        about_resource.Pass(), start_changestamp, callback);
294  }
295}
296
297void ChangeListLoader::OnGetAppList(google_apis::GDataErrorCode status,
298                                    scoped_ptr<google_apis::AppList> app_list) {
299  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300
301  DriveFileError error = util::GDataToDriveFileError(status);
302  if (error != DRIVE_FILE_OK)
303    return;
304
305  if (app_list.get()) {
306    webapps_registry_->UpdateFromAppList(*app_list);
307  }
308}
309
310void ChangeListLoader::LoadFromServer(scoped_ptr<LoadFeedParams> params,
311                                      const LoadFeedListCallback& callback) {
312  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
313  DCHECK(!callback.is_null());
314
315  const base::TimeTicks start_time = base::TimeTicks::Now();
316
317  // base::Passed() may get evaluated first, so get a pointer to params.
318  LoadFeedParams* params_ptr = params.get();
319  scheduler_->GetResourceList(
320      params_ptr->feed_to_load,
321      params_ptr->start_changestamp,
322      params_ptr->search_query,
323      params_ptr->shared_with_me,
324      params_ptr->directory_resource_id,
325      base::Bind(&ChangeListLoader::LoadFromServerAfterGetResourceList,
326                 weak_ptr_factory_.GetWeakPtr(),
327                 base::Passed(&params),
328                 callback,
329                 start_time));
330}
331
332void ChangeListLoader::LoadDirectoryFromServer(
333    const std::string& directory_resource_id,
334    const FileOperationCallback& callback) {
335  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
336  DCHECK(!callback.is_null());
337
338  // First fetch the latest changestamp to see if this directory needs to be
339  // updated.
340  scheduler_->GetAboutResource(
341      base::Bind(
342          &ChangeListLoader::LoadDirectoryFromServerAfterGetAbout,
343          weak_ptr_factory_.GetWeakPtr(),
344          directory_resource_id,
345          callback));
346}
347
348void ChangeListLoader::LoadDirectoryFromServerAfterGetAbout(
349      const std::string& directory_resource_id,
350      const FileOperationCallback& callback,
351      google_apis::GDataErrorCode status,
352      scoped_ptr<google_apis::AboutResource> about_resource) {
353  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354  DCHECK(!callback.is_null());
355
356  int64 remote_changestamp = 0;
357  if (util::GDataToDriveFileError(status) == DRIVE_FILE_OK) {
358    DCHECK(about_resource);
359    remote_changestamp = about_resource->largest_change_id();
360  }
361
362  const DirectoryFetchInfo directory_fetch_info(directory_resource_id,
363                                                remote_changestamp);
364  DoLoadDirectoryFromServer(directory_fetch_info, callback);
365}
366
367void ChangeListLoader::DoLoadDirectoryFromServer(
368    const DirectoryFetchInfo& directory_fetch_info,
369    const FileOperationCallback& callback) {
370  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
371  DCHECK(!callback.is_null());
372  DCHECK(!directory_fetch_info.empty());
373  DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
374
375  scoped_ptr<LoadFeedParams> params(new LoadFeedParams);
376  params->directory_resource_id = directory_fetch_info.resource_id();
377  LoadFromServer(
378      params.Pass(),
379      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterLoad,
380                 weak_ptr_factory_.GetWeakPtr(),
381                 directory_fetch_info,
382                 callback));
383}
384
385void ChangeListLoader::DoLoadDirectoryFromServerAfterLoad(
386    const DirectoryFetchInfo& directory_fetch_info,
387    const FileOperationCallback& callback,
388    const ScopedVector<google_apis::ResourceList>& resource_list,
389    DriveFileError error) {
390  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
391  DCHECK(!callback.is_null());
392  DCHECK(!directory_fetch_info.empty());
393
394  if (error != DRIVE_FILE_OK) {
395    LOG(ERROR) << "Failed to load directory: "
396               << directory_fetch_info.resource_id()
397               << ": " << error;
398    callback.Run(error);
399    return;
400  }
401
402  // Do not use |change_list_processor_| as it may be in use for other
403  // purposes.
404  ChangeListProcessor change_list_processor(resource_metadata_);
405  change_list_processor.FeedToEntryProtoMap(resource_list, NULL, NULL);
406  resource_metadata_->RefreshDirectory(
407      directory_fetch_info,
408      change_list_processor.entry_proto_map(),
409      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh,
410                 weak_ptr_factory_.GetWeakPtr(),
411                 directory_fetch_info,
412                 callback));
413}
414
415void ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh(
416    const DirectoryFetchInfo& directory_fetch_info,
417    const FileOperationCallback& callback,
418    DriveFileError error,
419    const base::FilePath& directory_path) {
420  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
421  DCHECK(!callback.is_null());
422
423  DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
424  callback.Run(error);
425  // Also notify the observers.
426  if (error == DRIVE_FILE_OK) {
427    FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
428                      OnDirectoryChanged(directory_path));
429  }
430}
431
432void ChangeListLoader::SearchFromServer(
433    const std::string& search_query,
434    bool shared_with_me,
435    const GURL& next_feed,
436    const LoadFeedListCallback& feed_load_callback) {
437  DCHECK(!feed_load_callback.is_null());
438
439  scoped_ptr<LoadFeedParams> params(new LoadFeedParams);
440  params->search_query = search_query;
441  params->shared_with_me = shared_with_me;
442  params->feed_to_load = next_feed;
443  params->load_subsequent_feeds = false;
444  LoadFromServer(params.Pass(), feed_load_callback);
445}
446
447void ChangeListLoader::UpdateMetadataFromFeedAfterLoadFromServer(
448    scoped_ptr<google_apis::AboutResource> about_resource,
449    bool is_delta_feed,
450    const FileOperationCallback& callback,
451    const ScopedVector<google_apis::ResourceList>& feed_list,
452    DriveFileError error) {
453  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
454  DCHECK(!callback.is_null());
455  DCHECK(refreshing_);
456
457  if (error != DRIVE_FILE_OK) {
458    OnChangeListLoadComplete(callback, error);
459    return;
460  }
461
462  UpdateFromFeed(about_resource.Pass(),
463                 feed_list,
464                 is_delta_feed,
465                 base::Bind(&ChangeListLoader::OnUpdateFromFeed,
466                            weak_ptr_factory_.GetWeakPtr(),
467                            !loaded(),  // is_initial_load
468                            callback));
469}
470
471void ChangeListLoader::LoadFromServerAfterGetResourceList(
472    scoped_ptr<LoadFeedParams> params,
473    const LoadFeedListCallback& callback,
474    base::TimeTicks start_time,
475    google_apis::GDataErrorCode status,
476    scoped_ptr<google_apis::ResourceList> resource_list) {
477  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
478  DCHECK(!callback.is_null());
479
480  if (params->feed_list.empty()) {
481    UMA_HISTOGRAM_TIMES("Drive.InitialFeedLoadTime",
482                        base::TimeTicks::Now() - start_time);
483  }
484
485  DriveFileError error = util::GDataToDriveFileError(status);
486  if (error != DRIVE_FILE_OK) {
487    callback.Run(params->feed_list, error);
488    return;
489  }
490  DCHECK(resource_list);
491
492  GURL next_feed_url;
493  const bool has_next_feed_url =
494      params->load_subsequent_feeds &&
495      resource_list->GetNextFeedURL(&next_feed_url);
496
497  // Add the current feed to the list of collected feeds for this directory.
498  params->feed_list.push_back(resource_list.release());
499
500  // Compute and notify the number of entries fetched so far.
501  int num_accumulated_entries = 0;
502  for (size_t i = 0; i < params->feed_list.size(); ++i)
503    num_accumulated_entries += params->feed_list[i]->entries().size();
504
505  // Check if we need to collect more data to complete the directory list.
506  if (has_next_feed_url && !next_feed_url.is_empty()) {
507    // Post an UI update event to make the UI smoother.
508    GetResourceListUiState* ui_state = params->ui_state.get();
509    if (ui_state == NULL) {
510      ui_state = new GetResourceListUiState(base::TimeTicks::Now());
511      params->ui_state.reset(ui_state);
512    }
513    DCHECK(ui_state);
514
515    if ((ui_state->num_fetched_documents - ui_state->num_showing_documents)
516        < kFetchUiUpdateStep) {
517      // Currently the UI update is stopped. Start UI periodic callback.
518      base::MessageLoopProxy::current()->PostTask(
519          FROM_HERE,
520          base::Bind(&ChangeListLoader::OnNotifyResourceListFetched,
521                     weak_ptr_factory_.GetWeakPtr(),
522                     ui_state->weak_ptr_factory.GetWeakPtr()));
523    }
524    ui_state->num_fetched_documents = num_accumulated_entries;
525    ui_state->feed_fetching_elapsed_time = base::TimeTicks::Now() - start_time;
526
527    // |params| will be passed to the callback and thus nulled. Extract the
528    // pointer so we can use it bellow.
529    LoadFeedParams* params_ptr = params.get();
530    // Kick off the remaining part of the feeds.
531    scheduler_->GetResourceList(
532        next_feed_url,
533        params_ptr->start_changestamp,
534        params_ptr->search_query,
535        params_ptr->shared_with_me,
536        params_ptr->directory_resource_id,
537        base::Bind(&ChangeListLoader::LoadFromServerAfterGetResourceList,
538                   weak_ptr_factory_.GetWeakPtr(),
539                   base::Passed(&params),
540                   callback,
541                   start_time));
542    return;
543  }
544
545  // Notify the observers that all document feeds are fetched.
546  FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
547                    OnResourceListFetched(num_accumulated_entries));
548
549  UMA_HISTOGRAM_TIMES("Drive.EntireFeedLoadTime",
550                      base::TimeTicks::Now() - start_time);
551
552  // Run the callback so the client can process the retrieved feeds.
553  callback.Run(params->feed_list, DRIVE_FILE_OK);
554}
555
556void ChangeListLoader::OnNotifyResourceListFetched(
557    base::WeakPtr<GetResourceListUiState> ui_state) {
558  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
559
560  if (!ui_state) {
561    // The ui state instance is already released, which means the fetching
562    // is done and we don't need to update any more.
563    return;
564  }
565
566  base::TimeDelta ui_elapsed_time =
567      base::TimeTicks::Now() - ui_state->start_time;
568
569  if (ui_state->num_showing_documents + kFetchUiUpdateStep <=
570      ui_state->num_fetched_documents) {
571    ui_state->num_showing_documents += kFetchUiUpdateStep;
572    FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
573                      OnResourceListFetched(ui_state->num_showing_documents));
574
575    int num_remaining_ui_updates =
576        (ui_state->num_fetched_documents - ui_state->num_showing_documents)
577        / kFetchUiUpdateStep;
578    if (num_remaining_ui_updates > 0) {
579      // Heuristically, we use fetched time duration to calculate the next
580      // UI update timing.
581      base::TimeDelta remaining_duration =
582          ui_state->feed_fetching_elapsed_time - ui_elapsed_time;
583      base::TimeDelta interval = remaining_duration / num_remaining_ui_updates;
584      // If UI update is slow for some reason, the interval can be
585      // negative, or very small. This rarely happens but should be handled.
586      const int kMinIntervalMs = 10;
587      if (interval.InMilliseconds() < kMinIntervalMs)
588        interval = base::TimeDelta::FromMilliseconds(kMinIntervalMs);
589
590      base::MessageLoopProxy::current()->PostDelayedTask(
591          FROM_HERE,
592          base::Bind(&ChangeListLoader::OnNotifyResourceListFetched,
593                     weak_ptr_factory_.GetWeakPtr(),
594                     ui_state->weak_ptr_factory.GetWeakPtr()),
595          interval);
596    }
597  }
598}
599
600void ChangeListLoader::LoadIfNeeded(
601    const DirectoryFetchInfo& directory_fetch_info,
602    const FileOperationCallback& callback) {
603  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
604  DCHECK(!callback.is_null());
605
606  // If feed has already been loaded, for normal feed fetch (= empty
607  // directory_fetch_info), we have nothing to do. For "fast fetch", we need to
608  // schedule a fetching if a feed refresh is currently running, because we
609  // don't want to wait a possibly large delta feed to arrive.
610  if (loaded() && (directory_fetch_info.empty() || !refreshing())) {
611    base::MessageLoopProxy::current()->PostTask(
612        FROM_HERE,
613        base::Bind(callback, DRIVE_FILE_OK));
614    return;
615  }
616
617  // At this point, it is either !loaded() or refreshing().
618  // If the change list loading is in progress, schedule the callback to
619  // run when it's ready (i.e. when the entire resource list is loaded, or
620  // the directory contents are available per "fast fetch").
621  if (refreshing()) {
622    ScheduleRun(directory_fetch_info, callback);
623    return;
624  }
625
626  if (!directory_fetch_info.empty()) {
627    // Add a dummy task to so ScheduleRun() can check that the directory is
628    // being fetched. This will be cleared either in
629    // ProcessPendingLoadCallbackForDirectory() or FlushPendingLoadCallback().
630    pending_load_callback_[directory_fetch_info.resource_id()].push_back(
631        base::Bind(&util::EmptyFileOperationCallback));
632  }
633
634  // First start loading from the cache.
635  LoadFromCache(base::Bind(&ChangeListLoader::LoadAfterLoadFromCache,
636                           weak_ptr_factory_.GetWeakPtr(),
637                           directory_fetch_info,
638                           callback));
639}
640
641void ChangeListLoader::LoadAfterLoadFromCache(
642    const DirectoryFetchInfo& directory_fetch_info,
643    const FileOperationCallback& callback,
644    DriveFileError error) {
645  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
646  DCHECK(!callback.is_null());
647
648  if (error == DRIVE_FILE_OK) {
649    loaded_ = true;
650
651    // The loading from the cache file succeeded. Change the refreshing state
652    // and tell the callback that the loading was successful.
653    OnChangeListLoadComplete(callback, DRIVE_FILE_OK);
654    FOR_EACH_OBSERVER(ChangeListLoaderObserver,
655                      observers_,
656                      OnInitialFeedLoaded());
657
658    // Load from server if needed (i.e. the cache is old). Note that we
659    // should still propagate |directory_fetch_info| though the directory is
660    // loaded first. This way, the UI can get notified via a directory change
661    // event as soon as the current directory contents are fetched.
662    LoadFromServerIfNeeded(directory_fetch_info,
663                           base::Bind(&util::EmptyFileOperationCallback));
664  } else {
665    // The loading from the cache file failed. Start loading from the
666    // server. Though the function name ends with "IfNeeded", this function
667    // should always start loading as the local changestamp is zero now.
668    LoadFromServerIfNeeded(directory_fetch_info, callback);
669  }
670}
671
672void ChangeListLoader::LoadFromCache(const FileOperationCallback& callback) {
673  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
674  DCHECK(!callback.is_null());
675  DCHECK(!loaded_);
676
677  // Sets the refreshing flag, so that the caller does not send refresh requests
678  // in parallel (see DriveFileSystem::LoadFeedIfNeeded).
679  //
680  // The flag will be unset when loading from the cache is complete, or
681  // loading from the server is complete.
682  refreshing_ = true;
683
684  resource_metadata_->Load(callback);
685}
686
687void ChangeListLoader::SaveFileSystem() {
688  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
689
690  resource_metadata_->MaybeSave();
691}
692
693void ChangeListLoader::UpdateFromFeed(
694    scoped_ptr<google_apis::AboutResource> about_resource,
695    const ScopedVector<google_apis::ResourceList>& feed_list,
696    bool is_delta_feed,
697    const base::Closure& update_finished_callback) {
698  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
699  DCHECK(!update_finished_callback.is_null());
700  DVLOG(1) << "Updating directory with a feed";
701
702  change_list_processor_.reset(new ChangeListProcessor(resource_metadata_));
703  // Don't send directory content change notification while performing
704  // the initial content retrieval.
705  const bool should_notify_changed_directories = is_delta_feed;
706
707  change_list_processor_->ApplyFeeds(
708      about_resource.Pass(),
709      feed_list,
710      is_delta_feed,
711      base::Bind(&ChangeListLoader::NotifyDirectoryChangedAfterApplyFeed,
712                 weak_ptr_factory_.GetWeakPtr(),
713                 should_notify_changed_directories,
714                 update_finished_callback));
715}
716
717void ChangeListLoader::ScheduleRun(
718    const DirectoryFetchInfo& directory_fetch_info,
719    const FileOperationCallback& callback) {
720  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
721  DCHECK(!callback.is_null());
722  DCHECK(refreshing_);
723
724  if (directory_fetch_info.empty()) {
725    // If the caller is not interested in a particular directory, just add the
726    // callback to the pending list and return.
727    pending_load_callback_[""].push_back(callback);
728    return;
729  }
730
731  const std::string& resource_id = directory_fetch_info.resource_id();
732
733  // If the directory of interest is already scheduled to be fetched, add the
734  // callback to the pending list and return.
735  LoadCallbackMap::iterator it = pending_load_callback_.find(resource_id);
736  if (it != pending_load_callback_.end()) {
737    it->second.push_back(callback);
738    return;
739  }
740
741  // If the directory's changestamp is up-to-date, just schedule to run the
742  // callback, as there is no need to fetch the directory.
743  if (directory_fetch_info.changestamp() >= last_known_remote_changestamp_) {
744    base::MessageLoopProxy::current()->PostTask(
745        FROM_HERE,
746        base::Bind(callback, DRIVE_FILE_OK));
747    return;
748  }
749
750  // The directory should be fetched. Add a dummy task to so ScheduleRun()
751  // can check that the directory is being fetched.
752  pending_load_callback_[resource_id].push_back(
753      base::Bind(&util::EmptyFileOperationCallback));
754  DoLoadDirectoryFromServer(
755      directory_fetch_info,
756      base::Bind(&ChangeListLoader::OnDirectoryLoadComplete,
757                 weak_ptr_factory_.GetWeakPtr(),
758                 directory_fetch_info,
759                 callback));
760}
761
762void ChangeListLoader::NotifyDirectoryChangedAfterApplyFeed(
763    bool should_notify_changed_directories,
764    const base::Closure& update_finished_callback) {
765  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
766  DCHECK(change_list_processor_.get());
767  DCHECK(!update_finished_callback.is_null());
768
769  loaded_ = true;
770
771  if (should_notify_changed_directories) {
772    for (std::set<base::FilePath>::iterator dir_iter =
773            change_list_processor_->changed_dirs().begin();
774        dir_iter != change_list_processor_->changed_dirs().end();
775        ++dir_iter) {
776      FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
777                        OnDirectoryChanged(*dir_iter));
778    }
779  }
780
781  update_finished_callback.Run();
782
783  // Cannot delete change_list_processor_ yet because we are in
784  // on_complete_callback_, which is owned by change_list_processor_.
785}
786
787void ChangeListLoader::OnUpdateFromFeed(
788    bool is_inital_load,
789    const FileOperationCallback& callback) {
790  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
791  DCHECK(!callback.is_null());
792
793  OnChangeListLoadComplete(callback, DRIVE_FILE_OK);
794  if (is_inital_load) {
795    FOR_EACH_OBSERVER(ChangeListLoaderObserver,
796                      observers_,
797                      OnInitialFeedLoaded());
798  }
799
800  // Save file system metadata to disk.
801  SaveFileSystem();
802
803  FOR_EACH_OBSERVER(ChangeListLoaderObserver,
804                    observers_,
805                    OnFeedFromServerLoaded());
806}
807
808void ChangeListLoader::OnChangeListLoadComplete(
809    const FileOperationCallback& callback,
810    DriveFileError error) {
811  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
812  DCHECK(!callback.is_null());
813
814  refreshing_ = false;
815  callback.Run(error);
816  FlushPendingLoadCallback(error);
817}
818
819void ChangeListLoader::OnDirectoryLoadComplete(
820    const DirectoryFetchInfo& directory_fetch_info,
821    const FileOperationCallback& callback,
822    DriveFileError error) {
823  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
824  DCHECK(!callback.is_null());
825
826  callback.Run(error);
827  ProcessPendingLoadCallbackForDirectory(directory_fetch_info.resource_id(),
828                                         error);
829}
830
831void ChangeListLoader::FlushPendingLoadCallback(DriveFileError error) {
832  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
833  DCHECK(!refreshing_);
834
835  for (LoadCallbackMap::iterator it = pending_load_callback_.begin();
836       it != pending_load_callback_.end();  ++it) {
837    const std::vector<FileOperationCallback>& callbacks = it->second;
838    for (size_t i = 0; i < callbacks.size(); ++i) {
839      base::MessageLoopProxy::current()->PostTask(
840          FROM_HERE,
841          base::Bind(callbacks[i], error));
842    }
843  }
844  pending_load_callback_.clear();
845}
846
847void ChangeListLoader::ProcessPendingLoadCallbackForDirectory(
848    const std::string& resource_id,
849    DriveFileError error) {
850  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
851
852  LoadCallbackMap::iterator it = pending_load_callback_.find(resource_id);
853  if (it != pending_load_callback_.end()) {
854    DVLOG(1) << "Running callback for " << resource_id;
855    const std::vector<FileOperationCallback>& callbacks = it->second;
856    for (size_t i = 0; i < callbacks.size(); ++i) {
857      base::MessageLoopProxy::current()->PostTask(
858          FROM_HERE,
859          base::Bind(callbacks[i], error));
860    }
861    pending_load_callback_.erase(it);
862  }
863}
864
865}  // namespace drive
866