change_list_loader.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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 "chrome/browser/chromeos/drive/change_list_loader_observer.h"
14#include "chrome/browser/chromeos/drive/change_list_processor.h"
15#include "chrome/browser/chromeos/drive/file_system_util.h"
16#include "chrome/browser/chromeos/drive/job_scheduler.h"
17#include "chrome/browser/chromeos/drive/logging.h"
18#include "chrome/browser/chromeos/drive/resource_metadata.h"
19#include "chrome/browser/drive/drive_api_util.h"
20#include "chrome/browser/drive/drive_service_interface.h"
21#include "chrome/browser/google_apis/drive_api_parser.h"
22#include "content/public/browser/browser_thread.h"
23#include "url/gurl.h"
24
25using content::BrowserThread;
26
27namespace drive {
28namespace internal {
29
30typedef base::Callback<void(FileError, ScopedVector<ChangeList>)>
31    FeedFetcherCallback;
32
33class ChangeListLoader::FeedFetcher {
34 public:
35  virtual ~FeedFetcher() {}
36  virtual void Run(const FeedFetcherCallback& callback) = 0;
37};
38
39namespace {
40
41// Fetches all the (currently available) resource entries from the server.
42class FullFeedFetcher : public ChangeListLoader::FeedFetcher {
43 public:
44  explicit FullFeedFetcher(JobScheduler* scheduler)
45      : scheduler_(scheduler),
46        weak_ptr_factory_(this) {
47  }
48
49  virtual ~FullFeedFetcher() {
50  }
51
52  virtual void Run(const FeedFetcherCallback& callback) OVERRIDE {
53    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
54    DCHECK(!callback.is_null());
55
56    // Rememeber the time stamp for usage stats.
57    start_time_ = base::TimeTicks::Now();
58
59    // This is full resource list fetch.
60    scheduler_->GetAllResourceList(
61        base::Bind(&FullFeedFetcher::OnFileListFetched,
62                   weak_ptr_factory_.GetWeakPtr(), callback));
63  }
64
65 private:
66  void OnFileListFetched(
67      const FeedFetcherCallback& callback,
68      google_apis::GDataErrorCode status,
69      scoped_ptr<google_apis::ResourceList> resource_list) {
70    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
71    DCHECK(!callback.is_null());
72
73    // Looks the UMA stats we take here is useless as many methods use this
74    // callback. crbug.com/229407
75    if (change_lists_.empty()) {
76      UMA_HISTOGRAM_TIMES("Drive.InitialFeedLoadTime",
77                          base::TimeTicks::Now() - start_time_);
78    }
79
80    FileError error = GDataToFileError(status);
81    if (error != FILE_ERROR_OK) {
82      callback.Run(error, ScopedVector<ChangeList>());
83      return;
84    }
85
86    DCHECK(resource_list);
87    change_lists_.push_back(new ChangeList(*resource_list));
88
89    GURL next_url;
90    if (resource_list->GetNextFeedURL(&next_url) && !next_url.is_empty()) {
91      // There is the remaining result so fetch it.
92      scheduler_->GetRemainingFileList(
93          next_url,
94          base::Bind(&FullFeedFetcher::OnFileListFetched,
95                     weak_ptr_factory_.GetWeakPtr(), callback));
96      return;
97    }
98
99    // This UMA stats looks also different from what we want. crbug.com/229407
100    UMA_HISTOGRAM_TIMES("Drive.EntireFeedLoadTime",
101                        base::TimeTicks::Now() - start_time_);
102
103    // Note: The fetcher is managed by ChangeListLoader, and the instance
104    // will be deleted in the callback. Do not touch the fields after this
105    // invocation.
106    callback.Run(FILE_ERROR_OK, change_lists_.Pass());
107  }
108
109  JobScheduler* scheduler_;
110  ScopedVector<ChangeList> change_lists_;
111  base::TimeTicks start_time_;
112  base::WeakPtrFactory<FullFeedFetcher> weak_ptr_factory_;
113  DISALLOW_COPY_AND_ASSIGN(FullFeedFetcher);
114};
115
116// Fetches the delta changes since |start_change_id|.
117class DeltaFeedFetcher : public ChangeListLoader::FeedFetcher {
118 public:
119  DeltaFeedFetcher(JobScheduler* scheduler, int64 start_change_id)
120      : scheduler_(scheduler),
121        start_change_id_(start_change_id),
122        weak_ptr_factory_(this) {
123  }
124
125  virtual ~DeltaFeedFetcher() {
126  }
127
128  virtual void Run(const FeedFetcherCallback& callback) OVERRIDE {
129    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
130    DCHECK(!callback.is_null());
131
132    scheduler_->GetChangeList(
133        start_change_id_,
134        base::Bind(&DeltaFeedFetcher::OnChangeListFetched,
135                   weak_ptr_factory_.GetWeakPtr(), callback));
136  }
137
138 private:
139  void OnChangeListFetched(
140      const FeedFetcherCallback& callback,
141      google_apis::GDataErrorCode status,
142      scoped_ptr<google_apis::ResourceList> resource_list) {
143    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
144    DCHECK(!callback.is_null());
145
146    FileError error = GDataToFileError(status);
147    if (error != FILE_ERROR_OK) {
148      callback.Run(error, ScopedVector<ChangeList>());
149      return;
150    }
151
152    DCHECK(resource_list);
153    change_lists_.push_back(new ChangeList(*resource_list));
154
155    GURL next_url;
156    if (resource_list->GetNextFeedURL(&next_url) && !next_url.is_empty()) {
157      // There is the remaining result so fetch it.
158      scheduler_->GetRemainingChangeList(
159          next_url,
160          base::Bind(&DeltaFeedFetcher::OnChangeListFetched,
161                     weak_ptr_factory_.GetWeakPtr(), callback));
162      return;
163    }
164
165    // Note: The fetcher is managed by ChangeListLoader, and the instance
166    // will be deleted in the callback. Do not touch the fields after this
167    // invocation.
168    callback.Run(FILE_ERROR_OK, change_lists_.Pass());
169  }
170
171  JobScheduler* scheduler_;
172  int64 start_change_id_;
173  ScopedVector<ChangeList> change_lists_;
174  base::WeakPtrFactory<DeltaFeedFetcher> weak_ptr_factory_;
175  DISALLOW_COPY_AND_ASSIGN(DeltaFeedFetcher);
176};
177
178// Fetches the resource entries in the directory with |directory_resource_id|.
179class FastFetchFeedFetcher : public ChangeListLoader::FeedFetcher {
180 public:
181  FastFetchFeedFetcher(JobScheduler* scheduler,
182                       DriveServiceInterface* drive_service,
183                       const std::string& directory_resource_id,
184                       const std::string& root_folder_id)
185      : scheduler_(scheduler),
186        drive_service_(drive_service),
187        directory_resource_id_(directory_resource_id),
188        root_folder_id_(root_folder_id),
189        weak_ptr_factory_(this) {
190  }
191
192  virtual ~FastFetchFeedFetcher() {
193  }
194
195  virtual void Run(const FeedFetcherCallback& callback) OVERRIDE {
196    if (util::IsDriveV2ApiEnabled() && root_folder_id_.empty()) {
197      // The root folder id is not available yet. Fetch from the server.
198      scheduler_->GetAboutResource(
199          base::Bind(&FastFetchFeedFetcher::RunAfterGetAboutResource,
200                     weak_ptr_factory_.GetWeakPtr(), callback));
201      return;
202    }
203
204    StartGetResourceListInDirectory(callback);
205  }
206
207 private:
208  void RunAfterGetAboutResource(
209      const FeedFetcherCallback& callback,
210      google_apis::GDataErrorCode status,
211      scoped_ptr<google_apis::AboutResource> about_resource) {
212    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
213    DCHECK(!callback.is_null());
214
215    FileError error = GDataToFileError(status);
216    if (error != FILE_ERROR_OK) {
217      callback.Run(error, ScopedVector<ChangeList>());
218      return;
219    }
220
221    root_folder_id_ = about_resource->root_folder_id();
222    StartGetResourceListInDirectory(callback);
223  }
224
225  void StartGetResourceListInDirectory(const FeedFetcherCallback& callback) {
226    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227    DCHECK(!callback.is_null());
228    DCHECK(!directory_resource_id_.empty());
229    DCHECK(!util::IsDriveV2ApiEnabled() || !root_folder_id_.empty());
230
231    // We use WAPI's GetResourceListInDirectory even if Drive API v2 is
232    // enabled. This is the short term work around of the performance
233    // regression.
234
235    std::string resource_id = directory_resource_id_;
236    if (util::IsDriveV2ApiEnabled() &&
237        directory_resource_id_ == root_folder_id_) {
238      // GData WAPI doesn't accept the root directory id which is used in Drive
239      // API v2. So it is necessary to translate it here.
240      resource_id = util::kWapiRootDirectoryResourceId;
241    }
242
243    scheduler_->GetResourceListInDirectoryByWapi(
244        resource_id,
245        base::Bind(&FastFetchFeedFetcher::OnResourceListFetched,
246                   weak_ptr_factory_.GetWeakPtr(), callback));
247  }
248
249  void OnResourceListFetched(
250      const FeedFetcherCallback& callback,
251      google_apis::GDataErrorCode status,
252      scoped_ptr<google_apis::ResourceList> resource_list) {
253    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254    DCHECK(!callback.is_null());
255
256    FileError error = GDataToFileError(status);
257    if (error != FILE_ERROR_OK) {
258      callback.Run(error, ScopedVector<ChangeList>());
259      return;
260    }
261
262    // Add the current change list to the list of collected lists.
263    DCHECK(resource_list);
264    ChangeList* change_list = new ChangeList(*resource_list);
265    if (util::IsDriveV2ApiEnabled())
266      FixResourceIdInChangeList(change_list);
267    change_lists_.push_back(change_list);
268
269    GURL next_url;
270    if (resource_list->GetNextFeedURL(&next_url) && !next_url.is_empty()) {
271      // There is the remaining result so fetch it.
272      scheduler_->GetRemainingResourceList(
273          next_url,
274          base::Bind(&FastFetchFeedFetcher::OnResourceListFetched,
275                     weak_ptr_factory_.GetWeakPtr(), callback));
276      return;
277    }
278
279    // Note: The fetcher is managed by ChangeListLoader, and the instance
280    // will be deleted in the callback. Do not touch the fields after this
281    // invocation.
282    callback.Run(FILE_ERROR_OK, change_lists_.Pass());
283  }
284
285  // Fixes resource IDs in |change_list| into the format that |drive_service_|
286  // can understand. Note that |change_list| contains IDs in GData WAPI format
287  // since currently we always use WAPI for fast fetch, regardless of the flag.
288  void FixResourceIdInChangeList(ChangeList* change_list) {
289    std::vector<ResourceEntry>* entries = change_list->mutable_entries();
290    std::vector<std::string>* parent_resource_ids =
291        change_list->mutable_parent_resource_ids();
292    for (size_t i = 0; i < entries->size(); ++i) {
293      ResourceEntry* entry = &(*entries)[i];
294      if (entry->has_resource_id())
295        entry->set_resource_id(FixResourceId(entry->resource_id()));
296
297      (*parent_resource_ids)[i] = FixResourceId((*parent_resource_ids)[i]);
298    }
299  }
300
301  std::string FixResourceId(const std::string& resource_id) {
302    if (resource_id == util::kWapiRootDirectoryResourceId)
303      return root_folder_id_;
304    return drive_service_->GetResourceIdCanonicalizer().Run(resource_id);
305  }
306
307  JobScheduler* scheduler_;
308  DriveServiceInterface* drive_service_;
309  std::string directory_resource_id_;
310  std::string root_folder_id_;
311  ScopedVector<ChangeList> change_lists_;
312  base::WeakPtrFactory<FastFetchFeedFetcher> weak_ptr_factory_;
313  DISALLOW_COPY_AND_ASSIGN(FastFetchFeedFetcher);
314};
315
316}  // namespace
317
318ChangeListLoader::ChangeListLoader(
319    base::SequencedTaskRunner* blocking_task_runner,
320    ResourceMetadata* resource_metadata,
321    JobScheduler* scheduler,
322    DriveServiceInterface* drive_service)
323    : blocking_task_runner_(blocking_task_runner),
324      resource_metadata_(resource_metadata),
325      scheduler_(scheduler),
326      drive_service_(drive_service),
327      last_known_remote_changestamp_(0),
328      loaded_(false),
329      weak_ptr_factory_(this) {
330}
331
332ChangeListLoader::~ChangeListLoader() {
333  STLDeleteElements(&fast_fetch_feed_fetcher_set_);
334}
335
336bool ChangeListLoader::IsRefreshing() const {
337  // Callback for change list loading is stored in pending_load_callback_[""].
338  // It is non-empty if and only if there is an in-flight loading operation.
339  return pending_load_callback_.find("") != pending_load_callback_.end();
340}
341
342void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) {
343  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
344  observers_.AddObserver(observer);
345}
346
347void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
348  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349  observers_.RemoveObserver(observer);
350}
351
352void ChangeListLoader::CheckForUpdates(const FileOperationCallback& callback) {
353  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354  DCHECK(!callback.is_null());
355
356  if (IsRefreshing()) {
357    // There is in-flight loading. So keep the callback here, and check for
358    // updates when the in-flight loading is completed.
359    pending_update_check_callback_ = callback;
360    return;
361  }
362
363  if (loaded_) {
364    // We only start to check for updates iff the load is done.
365    // I.e., we ignore checking updates if not loaded to avoid starting the
366    // load without user's explicit interaction (such as opening Drive).
367    util::Log(logging::LOG_INFO, "Checking for updates");
368    Load(DirectoryFetchInfo(), callback);
369  }
370}
371
372void ChangeListLoader::LoadIfNeeded(
373    const DirectoryFetchInfo& directory_fetch_info,
374    const FileOperationCallback& callback) {
375  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
376  DCHECK(!callback.is_null());
377
378  // If the resource metadata has been already loaded, for normal change list
379  // fetch (= empty directory_fetch_info), we have nothing to do. For "fast
380  // fetch", we need to schedule a fetching if a refresh is currently
381  // running, because we don't want to wait a possibly large delta change
382  // list to arrive.
383  if (loaded_ && (directory_fetch_info.empty() || !IsRefreshing())) {
384    base::MessageLoopProxy::current()->PostTask(
385        FROM_HERE,
386        base::Bind(callback, FILE_ERROR_OK));
387    return;
388  }
389  Load(directory_fetch_info, callback);
390}
391
392void ChangeListLoader::Load(const DirectoryFetchInfo& directory_fetch_info,
393                            const FileOperationCallback& callback) {
394  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395  DCHECK(!callback.is_null());
396
397  // Check if this is the first time this ChangeListLoader do loading.
398  // Note: IsRefreshing() depends on pending_load_callback_ so check in advance.
399  const bool is_initial_load = (!loaded_ && !IsRefreshing());
400
401  // Register the callback function to be called when it is loaded.
402  const std::string& resource_id = directory_fetch_info.resource_id();
403  pending_load_callback_[resource_id].push_back(callback);
404
405  // If loading task for |resource_id| is already running, do nothing.
406  if (pending_load_callback_[resource_id].size() > 1)
407    return;
408
409  // For initial loading, even for directory fetching, we do load the full
410  // resource list from the server to sync up. So we register a dummy
411  // callback to indicate that update for full hierarchy is running.
412  if (is_initial_load && !resource_id.empty()) {
413    pending_load_callback_[""].push_back(
414        base::Bind(&util::EmptyFileOperationCallback));
415  }
416
417  // Check the current status of local metadata, and start loading if needed.
418  base::PostTaskAndReplyWithResult(
419      blocking_task_runner_,
420      FROM_HERE,
421      base::Bind(&ResourceMetadata::GetLargestChangestamp,
422                 base::Unretained(resource_metadata_)),
423      base::Bind(is_initial_load ? &ChangeListLoader::DoInitialLoad
424                                 : &ChangeListLoader::DoUpdateLoad,
425                 weak_ptr_factory_.GetWeakPtr(),
426                 directory_fetch_info));
427}
428
429void ChangeListLoader::DoInitialLoad(
430    const DirectoryFetchInfo& directory_fetch_info,
431    int64 local_changestamp) {
432  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
433
434  if (local_changestamp > 0) {
435    // The local data is usable. Flush callbacks to tell loading was successful.
436    OnChangeListLoadComplete(FILE_ERROR_OK);
437
438    // Continues to load from server in background.
439    // Put dummy callbacks to indicate that fetching is still continuing.
440    pending_load_callback_[directory_fetch_info.resource_id()].push_back(
441        base::Bind(&util::EmptyFileOperationCallback));
442    if (!directory_fetch_info.empty()) {
443      pending_load_callback_[""].push_back(
444          base::Bind(&util::EmptyFileOperationCallback));
445    }
446  }
447  LoadFromServerIfNeeded(directory_fetch_info, local_changestamp);
448}
449
450void ChangeListLoader::DoUpdateLoad(
451    const DirectoryFetchInfo& directory_fetch_info,
452    int64 local_changestamp) {
453  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
454
455  if (directory_fetch_info.empty()) {
456    LoadFromServerIfNeeded(directory_fetch_info, local_changestamp);
457  } else {
458    // Note: CheckChangestampAndLoadDirectoryIfNeeded regards
459    // last_know_remote_changestamp_ as the remote changestamp. To be precise,
460    // we need to call GetAboutResource() here, as we do in other places like
461    // LoadFromServerIfNeeded or LoadFromDirectory. However,
462    // - It is costly to do GetAboutResource HTTP request every time.
463    // - The chance using an old value is small; it only happens when
464    //   LoadIfNeeded is called during one GetAboutResource roundtrip time
465    //   of a change list fetching.
466    // - Even if the value is old, it just marks the directory as older. It may
467    //   trigger one future unnecessary re-fetch, but it'll never lose data.
468    CheckChangestampAndLoadDirectoryIfNeeded(
469        directory_fetch_info,
470        local_changestamp,
471        base::Bind(&ChangeListLoader::OnDirectoryLoadComplete,
472                   weak_ptr_factory_.GetWeakPtr(),
473                   directory_fetch_info));
474  }
475}
476
477void ChangeListLoader::OnChangeListLoadComplete(FileError error) {
478  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
479
480  if (!loaded_ && error == FILE_ERROR_OK) {
481    loaded_ = true;
482    FOR_EACH_OBSERVER(ChangeListLoaderObserver,
483                      observers_,
484                      OnInitialLoadComplete());
485  }
486
487  for (LoadCallbackMap::iterator it = pending_load_callback_.begin();
488       it != pending_load_callback_.end();  ++it) {
489    const std::vector<FileOperationCallback>& callbacks = it->second;
490    for (size_t i = 0; i < callbacks.size(); ++i) {
491      base::MessageLoopProxy::current()->PostTask(
492          FROM_HERE,
493          base::Bind(callbacks[i], error));
494    }
495  }
496  pending_load_callback_.clear();
497
498  // If there is pending update check, try to load the change from the server
499  // again, because there may exist an update during the completed loading.
500  if (!pending_update_check_callback_.is_null()) {
501    Load(DirectoryFetchInfo(),
502         base::ResetAndReturn(&pending_update_check_callback_));
503  }
504}
505
506void ChangeListLoader::OnDirectoryLoadComplete(
507    const DirectoryFetchInfo& directory_fetch_info,
508    FileError error) {
509  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
510
511  util::Log(logging::LOG_INFO,
512            "Fast-fetch complete: %s => %s",
513            directory_fetch_info.ToString().c_str(),
514            FileErrorToString(error).c_str());
515  const std::string& resource_id = directory_fetch_info.resource_id();
516  LoadCallbackMap::iterator it = pending_load_callback_.find(resource_id);
517  if (it != pending_load_callback_.end()) {
518    DVLOG(1) << "Running callback for " << resource_id;
519    const std::vector<FileOperationCallback>& callbacks = it->second;
520    for (size_t i = 0; i < callbacks.size(); ++i) {
521      base::MessageLoopProxy::current()->PostTask(
522          FROM_HERE,
523          base::Bind(callbacks[i], error));
524    }
525    pending_load_callback_.erase(it);
526  }
527}
528
529void ChangeListLoader::LoadFromServerIfNeeded(
530    const DirectoryFetchInfo& directory_fetch_info,
531    int64 local_changestamp) {
532  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
533
534  // First fetch the latest changestamp to see if there were any new changes
535  // there at all.
536  scheduler_->GetAboutResource(
537      base::Bind(&ChangeListLoader::LoadFromServerIfNeededAfterGetAbout,
538                 weak_ptr_factory_.GetWeakPtr(),
539                 directory_fetch_info,
540                 local_changestamp));
541}
542
543void ChangeListLoader::LoadFromServerIfNeededAfterGetAbout(
544    const DirectoryFetchInfo& directory_fetch_info,
545    int64 local_changestamp,
546    google_apis::GDataErrorCode status,
547    scoped_ptr<google_apis::AboutResource> about_resource) {
548  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
549
550  FileError error = GDataToFileError(status);
551  if (error != FILE_ERROR_OK) {
552    OnChangeListLoadComplete(error);
553    return;
554  }
555
556  DCHECK(about_resource);
557  last_known_remote_changestamp_ = about_resource->largest_change_id();
558  root_folder_id_ = about_resource->root_folder_id();
559
560  int64 remote_changestamp = about_resource->largest_change_id();
561  if (remote_changestamp > 0 && local_changestamp >= remote_changestamp) {
562    if (local_changestamp > remote_changestamp) {
563      LOG(WARNING) << "Local resource metadata is fresher than server, local = "
564                   << local_changestamp << ", server = " << remote_changestamp;
565    }
566
567    // No changes detected, tell the client that the loading was successful.
568    OnChangeListLoadComplete(FILE_ERROR_OK);
569    return;
570  }
571
572  int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0;
573  if (directory_fetch_info.empty()) {
574    // If the caller is not interested in a particular directory, just start
575    // loading the change list.
576    LoadChangeListFromServer(about_resource.Pass(), start_changestamp);
577  } else {
578    // If the caller is interested in a particular directory, start loading the
579    // directory first.
580    CheckChangestampAndLoadDirectoryIfNeeded(
581        directory_fetch_info,
582        local_changestamp,
583        base::Bind(
584            &ChangeListLoader::LoadFromServerIfNeededAfterLoadDirectory,
585            weak_ptr_factory_.GetWeakPtr(),
586            directory_fetch_info,
587            base::Passed(&about_resource),
588            start_changestamp));
589  }
590}
591
592void ChangeListLoader::LoadFromServerIfNeededAfterLoadDirectory(
593    const DirectoryFetchInfo& directory_fetch_info,
594    scoped_ptr<google_apis::AboutResource> about_resource,
595    int64 start_changestamp,
596    FileError error) {
597  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
598  DCHECK(about_resource);
599
600  if (error == FILE_ERROR_OK) {
601    // The directory fast-fetch succeeded. Runs the callbacks waiting for the
602    // directory loading. If failed, do not flush so they're run after the
603    // change list loading is complete.
604    OnDirectoryLoadComplete(directory_fetch_info, FILE_ERROR_OK);
605  }
606  LoadChangeListFromServer(about_resource.Pass(), start_changestamp);
607}
608
609void ChangeListLoader::LoadChangeListFromServer(
610    scoped_ptr<google_apis::AboutResource> about_resource,
611    int64 start_changestamp) {
612  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
613  DCHECK(!change_feed_fetcher_);
614  DCHECK(about_resource);
615
616  bool is_delta_update = start_changestamp != 0;
617
618  // Set up feed fetcher.
619  if (is_delta_update) {
620    change_feed_fetcher_.reset(
621        new DeltaFeedFetcher(scheduler_, start_changestamp));
622  } else {
623    change_feed_fetcher_.reset(new FullFeedFetcher(scheduler_));
624  }
625
626  change_feed_fetcher_->Run(
627      base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList,
628                 weak_ptr_factory_.GetWeakPtr(),
629                 base::Passed(&about_resource),
630                 is_delta_update));
631}
632
633void ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList(
634    scoped_ptr<google_apis::AboutResource> about_resource,
635    bool is_delta_update,
636    FileError error,
637    ScopedVector<ChangeList> change_lists) {
638  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
639  DCHECK(about_resource);
640
641  // Delete the fetcher first.
642  change_feed_fetcher_.reset();
643
644  if (error != FILE_ERROR_OK) {
645    OnChangeListLoadComplete(error);
646    return;
647  }
648
649  UpdateFromChangeList(
650      about_resource.Pass(),
651      change_lists.Pass(),
652      is_delta_update,
653      base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterUpdate,
654                 weak_ptr_factory_.GetWeakPtr()));
655}
656
657void ChangeListLoader::LoadChangeListFromServerAfterUpdate() {
658  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
659
660  OnChangeListLoadComplete(FILE_ERROR_OK);
661
662  FOR_EACH_OBSERVER(ChangeListLoaderObserver,
663                    observers_,
664                    OnLoadFromServerComplete());
665}
666
667void ChangeListLoader::CheckChangestampAndLoadDirectoryIfNeeded(
668    const DirectoryFetchInfo& directory_fetch_info,
669    int64 local_changestamp,
670    const FileOperationCallback& callback) {
671  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
672  DCHECK(!directory_fetch_info.empty());
673
674  int64 directory_changestamp = std::max(directory_fetch_info.changestamp(),
675                                         local_changestamp);
676
677  // We may not fetch from the server at all if the local metadata is new
678  // enough, but we log this message here, so "Fast-fetch start" and
679  // "Fast-fetch complete" always match.
680  // TODO(satorux): Distinguish the "not fetching at all" case.
681  util::Log(logging::LOG_INFO,
682            "Fast-fetch start: %s; Server changestamp: %s",
683            directory_fetch_info.ToString().c_str(),
684            base::Int64ToString(last_known_remote_changestamp_).c_str());
685
686  // If the directory's changestamp is up-to-date, just schedule to run the
687  // callback, as there is no need to fetch the directory.
688  // Note that |last_known_remote_changestamp_| is 0 when it is not received
689  // yet. In that case we conservatively assume that we need to fetch.
690  if (last_known_remote_changestamp_ > 0 &&
691      directory_changestamp >= last_known_remote_changestamp_) {
692    callback.Run(FILE_ERROR_OK);
693    return;
694  }
695
696  // Start fetching the directory content, and mark it with the changestamp
697  // |last_known_remote_changestamp_|.
698  DirectoryFetchInfo new_directory_fetch_info(
699      directory_fetch_info.resource_id(),
700      std::max(directory_changestamp, last_known_remote_changestamp_));
701  DoLoadDirectoryFromServer(new_directory_fetch_info, callback);
702}
703
704void ChangeListLoader::DoLoadDirectoryFromServer(
705    const DirectoryFetchInfo& directory_fetch_info,
706    const FileOperationCallback& callback) {
707  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
708  DCHECK(!callback.is_null());
709  DCHECK(!directory_fetch_info.empty());
710  DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
711
712  if (directory_fetch_info.resource_id() ==
713          util::kDriveOtherDirSpecialResourceId) {
714    // Load for a <other> directory is meaningless in the server.
715    // Let it succeed and use what we have locally.
716    callback.Run(FILE_ERROR_OK);
717    return;
718  }
719
720  if (directory_fetch_info.resource_id() ==
721          util::kDriveGrandRootSpecialResourceId) {
722    // Load for a grand root directory means slightly different from other
723    // directories. It should have two directories; <other> and mydrive root.
724    // <other> directory should always exist, but mydrive root should be
725    // created by root resource id retrieved from the server.
726    // Here, we check if mydrive root exists, and if not, create it.
727    resource_metadata_->GetResourceEntryByPathOnUIThread(
728        base::FilePath(util::GetDriveMyDriveRootPath()),
729        base::Bind(
730            &ChangeListLoader
731                ::DoLoadGrandRootDirectoryFromServerAfterGetResourceEntryByPath,
732            weak_ptr_factory_.GetWeakPtr(),
733            directory_fetch_info,
734            callback));
735    return;
736  }
737
738  FastFetchFeedFetcher* fetcher = new FastFetchFeedFetcher(
739      scheduler_,
740      drive_service_,
741      directory_fetch_info.resource_id(),
742      root_folder_id_);
743  fast_fetch_feed_fetcher_set_.insert(fetcher);
744  fetcher->Run(
745      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterLoad,
746                 weak_ptr_factory_.GetWeakPtr(),
747                 directory_fetch_info,
748                 callback,
749                 fetcher));
750}
751
752void
753ChangeListLoader::DoLoadGrandRootDirectoryFromServerAfterGetResourceEntryByPath(
754    const DirectoryFetchInfo& directory_fetch_info,
755    const FileOperationCallback& callback,
756    FileError error,
757    scoped_ptr<ResourceEntry> entry) {
758  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
759  DCHECK(!callback.is_null());
760  DCHECK_EQ(directory_fetch_info.resource_id(),
761            util::kDriveGrandRootSpecialResourceId);
762
763  if (error == FILE_ERROR_OK) {
764    // MyDrive root already exists. Just return success.
765    callback.Run(FILE_ERROR_OK);
766    return;
767  }
768
769  // Fetch root resource id from the server.
770  scheduler_->GetAboutResource(
771      base::Bind(
772          &ChangeListLoader
773              ::DoLoadGrandRootDirectoryFromServerAfterGetAboutResource,
774          weak_ptr_factory_.GetWeakPtr(),
775          directory_fetch_info,
776          callback));
777}
778
779void ChangeListLoader::DoLoadGrandRootDirectoryFromServerAfterGetAboutResource(
780    const DirectoryFetchInfo& directory_fetch_info,
781    const FileOperationCallback& callback,
782    google_apis::GDataErrorCode status,
783    scoped_ptr<google_apis::AboutResource> about_resource) {
784  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
785  DCHECK(!callback.is_null());
786  DCHECK(about_resource);
787  DCHECK_EQ(directory_fetch_info.resource_id(),
788            util::kDriveGrandRootSpecialResourceId);
789
790  FileError error = GDataToFileError(status);
791  if (error != FILE_ERROR_OK) {
792    callback.Run(error);
793    return;
794  }
795
796  // Add "My Drive".
797  const std::string& root_resource_id = about_resource->root_folder_id();
798  std::string* local_id = new std::string;
799  base::PostTaskAndReplyWithResult(
800      blocking_task_runner_,
801      FROM_HERE,
802      base::Bind(&ResourceMetadata::AddEntry,
803                 base::Unretained(resource_metadata_),
804                 util::CreateMyDriveRootEntry(root_resource_id),
805                 local_id),
806      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterAddMyDrive,
807                 weak_ptr_factory_.GetWeakPtr(),
808                 directory_fetch_info,
809                 callback,
810                 base::Owned(local_id)));
811}
812
813void ChangeListLoader::DoLoadDirectoryFromServerAfterAddMyDrive(
814    const DirectoryFetchInfo& directory_fetch_info,
815    const FileOperationCallback& callback,
816    std::string* local_id,
817    FileError error) {
818  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
819  DCHECK(!callback.is_null());
820  DCHECK_EQ(directory_fetch_info.resource_id(),
821            util::kDriveGrandRootSpecialResourceId);
822
823  const base::FilePath changed_directory_path(util::GetDriveGrandRootPath());
824  DoLoadDirectoryFromServerAfterRefresh(directory_fetch_info,
825                                        callback,
826                                        &changed_directory_path,
827                                        error);
828}
829
830void ChangeListLoader::DoLoadDirectoryFromServerAfterLoad(
831    const DirectoryFetchInfo& directory_fetch_info,
832    const FileOperationCallback& callback,
833    FeedFetcher* fetcher,
834    FileError error,
835    ScopedVector<ChangeList> change_lists) {
836  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
837  DCHECK(!callback.is_null());
838  DCHECK(!directory_fetch_info.empty());
839
840  // Delete the fetcher.
841  fast_fetch_feed_fetcher_set_.erase(fetcher);
842  delete fetcher;
843
844  if (error != FILE_ERROR_OK) {
845    LOG(ERROR) << "Failed to load directory: "
846               << directory_fetch_info.resource_id()
847               << ": " << FileErrorToString(error);
848    callback.Run(error);
849    return;
850  }
851
852  base::FilePath* directory_path = new base::FilePath;
853  base::PostTaskAndReplyWithResult(
854      blocking_task_runner_,
855      FROM_HERE,
856      base::Bind(&ChangeListProcessor::RefreshDirectory,
857                 resource_metadata_,
858                 directory_fetch_info,
859                 base::Passed(&change_lists),
860                 directory_path),
861      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh,
862                 weak_ptr_factory_.GetWeakPtr(),
863                 directory_fetch_info,
864                 callback,
865                 base::Owned(directory_path)));
866}
867
868void ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh(
869    const DirectoryFetchInfo& directory_fetch_info,
870    const FileOperationCallback& callback,
871    const base::FilePath* directory_path,
872    FileError error) {
873  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
874  DCHECK(!callback.is_null());
875
876  DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
877  callback.Run(error);
878  // Also notify the observers.
879  if (error == FILE_ERROR_OK) {
880    FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
881                      OnDirectoryChanged(*directory_path));
882  }
883}
884
885void ChangeListLoader::UpdateFromChangeList(
886    scoped_ptr<google_apis::AboutResource> about_resource,
887    ScopedVector<ChangeList> change_lists,
888    bool is_delta_update,
889    const base::Closure& callback) {
890  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
891  DCHECK(!callback.is_null());
892  DCHECK(about_resource);
893
894  ChangeListProcessor* change_list_processor =
895      new ChangeListProcessor(resource_metadata_);
896  // Don't send directory content change notification while performing
897  // the initial content retrieval.
898  const bool should_notify_changed_directories = is_delta_update;
899
900  util::Log(logging::LOG_INFO,
901            "Apply change lists (is delta: %d)",
902            is_delta_update);
903  base::PostTaskAndReplyWithResult(
904      blocking_task_runner_,
905      FROM_HERE,
906      base::Bind(&ChangeListProcessor::Apply,
907                 base::Unretained(change_list_processor),
908                 base::Passed(&about_resource),
909                 base::Passed(&change_lists),
910                 is_delta_update),
911      base::Bind(&ChangeListLoader::UpdateFromChangeListAfterApply,
912                 weak_ptr_factory_.GetWeakPtr(),
913                 base::Owned(change_list_processor),
914                 should_notify_changed_directories,
915                 base::Time::Now(),
916                 callback));
917}
918
919void ChangeListLoader::UpdateFromChangeListAfterApply(
920    ChangeListProcessor* change_list_processor,
921    bool should_notify_changed_directories,
922    base::Time start_time,
923    const base::Closure& callback,
924    FileError error) {
925  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
926  DCHECK(change_list_processor);
927  DCHECK(!callback.is_null());
928
929  const base::TimeDelta elapsed = base::Time::Now() - start_time;
930  util::Log(logging::LOG_INFO,
931            "Change lists applied (elapsed time: %sms)",
932            base::Int64ToString(elapsed.InMilliseconds()).c_str());
933
934  if (should_notify_changed_directories) {
935    for (std::set<base::FilePath>::iterator dir_iter =
936            change_list_processor->changed_dirs().begin();
937        dir_iter != change_list_processor->changed_dirs().end();
938        ++dir_iter) {
939      FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
940                        OnDirectoryChanged(*dir_iter));
941    }
942  }
943
944  callback.Run();
945}
946
947}  // namespace internal
948}  // namespace drive
949