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/google_apis/drive_api_parser.h"
21#include "content/public/browser/browser_thread.h"
22#include "url/gurl.h"
23
24using content::BrowserThread;
25
26namespace drive {
27namespace internal {
28
29ChangeListLoader::ChangeListLoader(
30    base::SequencedTaskRunner* blocking_task_runner,
31    ResourceMetadata* resource_metadata,
32    JobScheduler* scheduler)
33    : blocking_task_runner_(blocking_task_runner),
34      resource_metadata_(resource_metadata),
35      scheduler_(scheduler),
36      last_known_remote_changestamp_(0),
37      loaded_(false),
38      weak_ptr_factory_(this) {
39}
40
41ChangeListLoader::~ChangeListLoader() {
42}
43
44bool ChangeListLoader::IsRefreshing() const {
45  // Callback for change list loading is stored in pending_load_callback_[""].
46  // It is non-empty if and only if there is an in-flight loading operation.
47  return pending_load_callback_.find("") != pending_load_callback_.end();
48}
49
50void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) {
51  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
52  observers_.AddObserver(observer);
53}
54
55void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
56  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
57  observers_.RemoveObserver(observer);
58}
59
60void ChangeListLoader::CheckForUpdates(const FileOperationCallback& callback) {
61  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
62  DCHECK(!callback.is_null());
63
64  if (IsRefreshing()) {
65    // There is in-flight loading. So keep the callback here, and check for
66    // updates when the in-flight loading is completed.
67    pending_update_check_callback_ = callback;
68    return;
69  }
70
71  if (loaded_) {
72    // We only start to check for updates iff the load is done.
73    // I.e., we ignore checking updates if not loaded to avoid starting the
74    // load without user's explicit interaction (such as opening Drive).
75    util::Log(logging::LOG_INFO, "Checking for updates");
76    Load(DirectoryFetchInfo(), callback);
77  }
78}
79
80void ChangeListLoader::LoadIfNeeded(
81    const DirectoryFetchInfo& directory_fetch_info,
82    const FileOperationCallback& callback) {
83  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
84  DCHECK(!callback.is_null());
85
86  // If the resource metadata has been already loaded, for normal change list
87  // fetch (= empty directory_fetch_info), we have nothing to do. For "fast
88  // fetch", we need to schedule a fetching if a refresh is currently
89  // running, because we don't want to wait a possibly large delta change
90  // list to arrive.
91  if (loaded_ && (directory_fetch_info.empty() || !IsRefreshing())) {
92    base::MessageLoopProxy::current()->PostTask(
93        FROM_HERE,
94        base::Bind(callback, FILE_ERROR_OK));
95    return;
96  }
97  Load(directory_fetch_info, callback);
98}
99
100void ChangeListLoader::LoadDirectoryFromServer(
101    const std::string& directory_resource_id,
102    const FileOperationCallback& callback) {
103  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
104  DCHECK(!callback.is_null());
105
106  scheduler_->GetAboutResource(
107      base::Bind(&ChangeListLoader::LoadDirectoryFromServerAfterGetAbout,
108                 weak_ptr_factory_.GetWeakPtr(),
109                 directory_resource_id,
110                 callback));
111}
112
113void ChangeListLoader::Load(const DirectoryFetchInfo& directory_fetch_info,
114                            const FileOperationCallback& callback) {
115  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
116  DCHECK(!callback.is_null());
117
118  // Check if this is the first time this ChangeListLoader do loading.
119  // Note: IsRefreshing() depends on pending_load_callback_ so check in advance.
120  const bool is_initial_load = (!loaded_ && !IsRefreshing());
121
122  // Register the callback function to be called when it is loaded.
123  const std::string& resource_id = directory_fetch_info.resource_id();
124  pending_load_callback_[resource_id].push_back(callback);
125
126  // If loading task for |resource_id| is already running, do nothing.
127  if (pending_load_callback_[resource_id].size() > 1)
128    return;
129
130  // For initial loading, even for directory fetching, we do load the full
131  // resource list from the server to sync up. So we register a dummy
132  // callback to indicate that update for full hierarchy is running.
133  if (is_initial_load && !resource_id.empty()) {
134    pending_load_callback_[""].push_back(
135        base::Bind(&util::EmptyFileOperationCallback));
136  }
137
138  // Check the current status of local metadata, and start loading if needed.
139  resource_metadata_->GetLargestChangestampOnUIThread(
140      base::Bind(is_initial_load ? &ChangeListLoader::DoInitialLoad
141                                 : &ChangeListLoader::DoUpdateLoad,
142                 weak_ptr_factory_.GetWeakPtr(),
143                 directory_fetch_info));
144}
145
146void ChangeListLoader::DoInitialLoad(
147    const DirectoryFetchInfo& directory_fetch_info,
148    int64 local_changestamp) {
149  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
150
151  if (local_changestamp > 0) {
152    // The local data is usable. Flush callbacks to tell loading was successful.
153    OnChangeListLoadComplete(FILE_ERROR_OK);
154
155    // Continues to load from server in background.
156    // Put dummy callbacks to indicate that fetching is still continuing.
157    pending_load_callback_[directory_fetch_info.resource_id()].push_back(
158        base::Bind(&util::EmptyFileOperationCallback));
159    if (!directory_fetch_info.empty()) {
160      pending_load_callback_[""].push_back(
161          base::Bind(&util::EmptyFileOperationCallback));
162    }
163  }
164  LoadFromServerIfNeeded(directory_fetch_info, local_changestamp);
165}
166
167void ChangeListLoader::DoUpdateLoad(
168    const DirectoryFetchInfo& directory_fetch_info,
169    int64 local_changestamp) {
170  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171
172  if (directory_fetch_info.empty()) {
173    LoadFromServerIfNeeded(directory_fetch_info, local_changestamp);
174  } else {
175    // Note: CheckChangestampAndLoadDirectoryIfNeeded regards
176    // last_know_remote_changestamp_ as the remote changestamp. To be precise,
177    // we need to call GetAboutResource() here, as we do in other places like
178    // LoadFromServerIfNeeded or LoadFromDirectory. However,
179    // - It is costly to do GetAboutResource HTTP request every time.
180    // - The chance using an old value is small; it only happens when
181    //   LoadIfNeeded is called during one GetAboutResource roundtrip time
182    //   of a change list fetching.
183    // - Even if the value is old, it just marks the directory as older. It may
184    //   trigger one future unnecessary re-fetch, but it'll never lose data.
185    CheckChangestampAndLoadDirectoryIfNeeded(
186        directory_fetch_info,
187        local_changestamp,
188        base::Bind(&ChangeListLoader::OnDirectoryLoadComplete,
189                   weak_ptr_factory_.GetWeakPtr(),
190                   directory_fetch_info));
191  }
192}
193
194void ChangeListLoader::OnChangeListLoadComplete(FileError error) {
195  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
196
197  if (!loaded_ && error == FILE_ERROR_OK) {
198    loaded_ = true;
199    FOR_EACH_OBSERVER(ChangeListLoaderObserver,
200                      observers_,
201                      OnInitialLoadComplete());
202  }
203
204  for (LoadCallbackMap::iterator it = pending_load_callback_.begin();
205       it != pending_load_callback_.end();  ++it) {
206    const std::vector<FileOperationCallback>& callbacks = it->second;
207    for (size_t i = 0; i < callbacks.size(); ++i) {
208      base::MessageLoopProxy::current()->PostTask(
209          FROM_HERE,
210          base::Bind(callbacks[i], error));
211    }
212  }
213  pending_load_callback_.clear();
214
215  // If there is pending update check, try to load the change from the server
216  // again, because there may exist an update during the completed loading.
217  if (!pending_update_check_callback_.is_null()) {
218    Load(DirectoryFetchInfo(),
219         base::ResetAndReturn(&pending_update_check_callback_));
220  }
221}
222
223void ChangeListLoader::OnDirectoryLoadComplete(
224    const DirectoryFetchInfo& directory_fetch_info,
225    FileError error) {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227
228  util::Log(logging::LOG_INFO,
229            "Fast-fetch complete: %s => %s",
230            directory_fetch_info.ToString().c_str(),
231            FileErrorToString(error).c_str());
232  const std::string& resource_id = directory_fetch_info.resource_id();
233  LoadCallbackMap::iterator it = pending_load_callback_.find(resource_id);
234  if (it != pending_load_callback_.end()) {
235    DVLOG(1) << "Running callback for " << resource_id;
236    const std::vector<FileOperationCallback>& callbacks = it->second;
237    for (size_t i = 0; i < callbacks.size(); ++i) {
238      base::MessageLoopProxy::current()->PostTask(
239          FROM_HERE,
240          base::Bind(callbacks[i], error));
241    }
242    pending_load_callback_.erase(it);
243  }
244}
245
246void ChangeListLoader::LoadFromServerIfNeeded(
247    const DirectoryFetchInfo& directory_fetch_info,
248    int64 local_changestamp) {
249  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
250
251  // First fetch the latest changestamp to see if there were any new changes
252  // there at all.
253  scheduler_->GetAboutResource(
254      base::Bind(&ChangeListLoader::LoadFromServerIfNeededAfterGetAbout,
255                 weak_ptr_factory_.GetWeakPtr(),
256                 directory_fetch_info,
257                 local_changestamp));
258}
259
260void ChangeListLoader::LoadFromServerIfNeededAfterGetAbout(
261    const DirectoryFetchInfo& directory_fetch_info,
262    int64 local_changestamp,
263    google_apis::GDataErrorCode status,
264    scoped_ptr<google_apis::AboutResource> about_resource) {
265  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
266  DCHECK_EQ(GDataToFileError(status) == FILE_ERROR_OK,
267            about_resource.get() != NULL);
268
269  if (GDataToFileError(status) == FILE_ERROR_OK) {
270    DCHECK(about_resource);
271    last_known_remote_changestamp_ = about_resource->largest_change_id();
272  }
273
274  int64 remote_changestamp =
275      about_resource ? about_resource->largest_change_id() : 0;
276  if (remote_changestamp > 0 && local_changestamp >= remote_changestamp) {
277    if (local_changestamp > remote_changestamp) {
278      LOG(WARNING) << "Local resource metadata is fresher than server, local = "
279                   << local_changestamp
280                   << ", server = "
281                   << remote_changestamp;
282    }
283
284    // No changes detected, tell the client that the loading was successful.
285    OnChangeListLoadComplete(FILE_ERROR_OK);
286    return;
287  }
288
289  int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0;
290  if (start_changestamp == 0 && !about_resource.get()) {
291    // Full update needs AboutResource. If this is a full update, we should
292    // just give up. Note that to exit from the change list loading, we
293    // always have to flush the pending callback tasks via
294    // OnChangeListLoadComplete.
295    OnChangeListLoadComplete(FILE_ERROR_FAILED);
296    return;
297  }
298
299  if (directory_fetch_info.empty()) {
300    // If the caller is not interested in a particular directory, just start
301    // loading the change list.
302    LoadChangeListFromServer(about_resource.Pass(), start_changestamp);
303  } else {
304    // If the caller is interested in a particular directory, start loading the
305    // directory first.
306    CheckChangestampAndLoadDirectoryIfNeeded(
307        directory_fetch_info,
308        local_changestamp,
309        base::Bind(
310            &ChangeListLoader::LoadFromServerIfNeededAfterLoadDirectory,
311            weak_ptr_factory_.GetWeakPtr(),
312            directory_fetch_info,
313            base::Passed(&about_resource),
314            start_changestamp));
315  }
316}
317
318void ChangeListLoader::LoadFromServerIfNeededAfterLoadDirectory(
319    const DirectoryFetchInfo& directory_fetch_info,
320    scoped_ptr<google_apis::AboutResource> about_resource,
321    int64 start_changestamp,
322    FileError error) {
323  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324
325  if (error == FILE_ERROR_OK) {
326    // The directory fast-fetch succeeded. Runs the callbacks waiting for the
327    // directory loading. If failed, do not flush so they're run after the
328    // change list loading is complete.
329    OnDirectoryLoadComplete(directory_fetch_info, FILE_ERROR_OK);
330  }
331  LoadChangeListFromServer(about_resource.Pass(), start_changestamp);
332}
333
334void ChangeListLoader::LoadChangeListFromServer(
335    scoped_ptr<google_apis::AboutResource> about_resource,
336    int64 start_changestamp) {
337  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
338
339  bool is_delta_update = start_changestamp != 0;
340  const LoadChangeListCallback& completion_callback =
341      base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList,
342                 weak_ptr_factory_.GetWeakPtr(),
343                 base::Passed(&about_resource),
344                 is_delta_update);
345  base::TimeTicks start_time = base::TimeTicks::Now();
346  if (is_delta_update) {
347    scheduler_->GetChangeList(
348        start_changestamp,
349        base::Bind(&ChangeListLoader::OnGetChangeList,
350                   weak_ptr_factory_.GetWeakPtr(),
351                   base::Passed(ScopedVector<ChangeList>()),
352                   completion_callback,
353                   start_time));
354  } else {
355    // This is full resource list fetch.
356    scheduler_->GetAllResourceList(
357        base::Bind(&ChangeListLoader::OnGetChangeList,
358                   weak_ptr_factory_.GetWeakPtr(),
359                   base::Passed(ScopedVector<ChangeList>()),
360                   completion_callback,
361                   start_time));
362  }
363}
364
365void ChangeListLoader::OnGetChangeList(
366    ScopedVector<ChangeList> change_lists,
367    const LoadChangeListCallback& callback,
368    base::TimeTicks start_time,
369    google_apis::GDataErrorCode status,
370    scoped_ptr<google_apis::ResourceList> resource_list) {
371  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
372  DCHECK(!callback.is_null());
373
374  // Looks the UMA stats we take here is useless as many methods use this
375  // callback. crbug.com/229407
376  if (change_lists.empty()) {
377    UMA_HISTOGRAM_TIMES("Drive.InitialFeedLoadTime",
378                        base::TimeTicks::Now() - start_time);
379  }
380
381  FileError error = GDataToFileError(status);
382  if (error != FILE_ERROR_OK) {
383    callback.Run(ScopedVector<ChangeList>(), error);
384    return;
385  }
386
387  // Add the current change list to the list of collected lists.
388  DCHECK(resource_list);
389  change_lists.push_back(new ChangeList(*resource_list));
390
391  GURL next_url;
392  if (resource_list->GetNextFeedURL(&next_url) &&
393      !next_url.is_empty()) {
394    // There is the remaining result so fetch it.
395    scheduler_->ContinueGetResourceList(
396        next_url,
397        base::Bind(&ChangeListLoader::OnGetChangeList,
398                   weak_ptr_factory_.GetWeakPtr(),
399                   base::Passed(&change_lists),
400                   callback,
401                   start_time));
402    return;
403  }
404
405  // This UMA stats looks also different from what we want. crbug.com/229407
406  UMA_HISTOGRAM_TIMES("Drive.EntireFeedLoadTime",
407                      base::TimeTicks::Now() - start_time);
408
409  // Run the callback so the client can process the retrieved change lists.
410  callback.Run(change_lists.Pass(), FILE_ERROR_OK);
411}
412
413void ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList(
414    scoped_ptr<google_apis::AboutResource> about_resource,
415    bool is_delta_update,
416    ScopedVector<ChangeList> change_lists,
417    FileError error) {
418  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
419
420  if (error != FILE_ERROR_OK) {
421    OnChangeListLoadComplete(error);
422    return;
423  }
424
425  UpdateFromChangeList(
426      about_resource.Pass(),
427      change_lists.Pass(),
428      is_delta_update,
429      base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterUpdate,
430                 weak_ptr_factory_.GetWeakPtr()));
431}
432
433void ChangeListLoader::LoadChangeListFromServerAfterUpdate() {
434  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
435
436  OnChangeListLoadComplete(FILE_ERROR_OK);
437
438  FOR_EACH_OBSERVER(ChangeListLoaderObserver,
439                    observers_,
440                    OnLoadFromServerComplete());
441}
442
443void ChangeListLoader::LoadDirectoryFromServerAfterGetAbout(
444    const std::string& directory_resource_id,
445    const FileOperationCallback& callback,
446    google_apis::GDataErrorCode status,
447    scoped_ptr<google_apis::AboutResource> about_resource) {
448  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
449  DCHECK(!callback.is_null());
450
451  if (GDataToFileError(status) == FILE_ERROR_OK)
452    last_known_remote_changestamp_ = about_resource->largest_change_id();
453
454  DoLoadDirectoryFromServer(
455      DirectoryFetchInfo(directory_resource_id, last_known_remote_changestamp_),
456      callback);
457}
458
459void ChangeListLoader::CheckChangestampAndLoadDirectoryIfNeeded(
460    const DirectoryFetchInfo& directory_fetch_info,
461    int64 local_changestamp,
462    const FileOperationCallback& callback) {
463  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
464  DCHECK(!directory_fetch_info.empty());
465
466  int64 directory_changestamp = std::max(directory_fetch_info.changestamp(),
467                                         local_changestamp);
468
469  // We may not fetch from the server at all if the local metadata is new
470  // enough, but we log this message here, so "Fast-fetch start" and
471  // "Fast-fetch complete" always match.
472  // TODO(satorux): Distinguish the "not fetching at all" case.
473  util::Log(logging::LOG_INFO,
474            "Fast-fetch start: %s; Server changestamp: %s",
475            directory_fetch_info.ToString().c_str(),
476            base::Int64ToString(last_known_remote_changestamp_).c_str());
477
478  // If the directory's changestamp is up-to-date, just schedule to run the
479  // callback, as there is no need to fetch the directory.
480  // Note that |last_known_remote_changestamp_| is 0 when it is not received
481  // yet. In that case we conservatively assume that we need to fetch.
482  if (last_known_remote_changestamp_ > 0 &&
483      directory_changestamp >= last_known_remote_changestamp_) {
484    callback.Run(FILE_ERROR_OK);
485    return;
486  }
487
488  // Start fetching the directory content, and mark it with the changestamp
489  // |last_known_remote_changestamp_|.
490  DirectoryFetchInfo new_directory_fetch_info(
491      directory_fetch_info.resource_id(),
492      std::max(directory_changestamp, last_known_remote_changestamp_));
493  DoLoadDirectoryFromServer(new_directory_fetch_info, callback);
494}
495
496void ChangeListLoader::DoLoadDirectoryFromServer(
497    const DirectoryFetchInfo& directory_fetch_info,
498    const FileOperationCallback& callback) {
499  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
500  DCHECK(!callback.is_null());
501  DCHECK(!directory_fetch_info.empty());
502  DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
503
504  if (directory_fetch_info.resource_id() ==
505          util::kDriveOtherDirSpecialResourceId) {
506    // Load for a <other> directory is meaningless in the server.
507    // Let it succeed and use what we have locally.
508    callback.Run(FILE_ERROR_OK);
509    return;
510  }
511
512  if (directory_fetch_info.resource_id() ==
513          util::kDriveGrandRootSpecialResourceId) {
514    // Load for a grand root directory means slightly different from other
515    // directories. It should have two directories; <other> and mydrive root.
516    // <other> directory should always exist, but mydrive root should be
517    // created by root resource id retrieved from the server.
518    // Here, we check if mydrive root exists, and if not, create it.
519    resource_metadata_->GetResourceEntryByPathOnUIThread(
520        base::FilePath(util::GetDriveMyDriveRootPath()),
521        base::Bind(
522            &ChangeListLoader
523                ::DoLoadGrandRootDirectoryFromServerAfterGetResourceEntryByPath,
524            weak_ptr_factory_.GetWeakPtr(),
525            directory_fetch_info,
526            callback));
527    return;
528  }
529
530  const LoadChangeListCallback& completion_callback =
531      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterLoad,
532                 weak_ptr_factory_.GetWeakPtr(),
533                 directory_fetch_info,
534                 callback);
535  base::TimeTicks start_time = base::TimeTicks::Now();
536  scheduler_->GetResourceListInDirectory(
537      directory_fetch_info.resource_id(),
538      base::Bind(&ChangeListLoader::OnGetChangeList,
539                 weak_ptr_factory_.GetWeakPtr(),
540                 base::Passed(ScopedVector<ChangeList>()),
541                 completion_callback,
542                 start_time));
543}
544
545void
546ChangeListLoader::DoLoadGrandRootDirectoryFromServerAfterGetResourceEntryByPath(
547    const DirectoryFetchInfo& directory_fetch_info,
548    const FileOperationCallback& callback,
549    FileError error,
550    scoped_ptr<ResourceEntry> entry) {
551  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
552  DCHECK(!callback.is_null());
553  DCHECK_EQ(directory_fetch_info.resource_id(),
554            util::kDriveGrandRootSpecialResourceId);
555
556  if (error == FILE_ERROR_OK) {
557    // MyDrive root already exists. Just return success.
558    callback.Run(FILE_ERROR_OK);
559    return;
560  }
561
562  // Fetch root resource id from the server.
563  scheduler_->GetAboutResource(
564      base::Bind(
565          &ChangeListLoader
566              ::DoLoadGrandRootDirectoryFromServerAfterGetAboutResource,
567          weak_ptr_factory_.GetWeakPtr(),
568          directory_fetch_info,
569          callback));
570}
571
572void ChangeListLoader::DoLoadGrandRootDirectoryFromServerAfterGetAboutResource(
573    const DirectoryFetchInfo& directory_fetch_info,
574    const FileOperationCallback& callback,
575    google_apis::GDataErrorCode status,
576    scoped_ptr<google_apis::AboutResource> about_resource) {
577  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
578  DCHECK(!callback.is_null());
579  DCHECK_EQ(directory_fetch_info.resource_id(),
580            util::kDriveGrandRootSpecialResourceId);
581
582  FileError error = GDataToFileError(status);
583  if (error != FILE_ERROR_OK) {
584    callback.Run(error);
585    return;
586  }
587
588  // Build entry map for grand root directory, which has two entries;
589  // "/drive/root" and "/drive/other".
590  ResourceEntryMap grand_root_entry_map;
591  const std::string& root_resource_id = about_resource->root_folder_id();
592  grand_root_entry_map[root_resource_id] =
593      util::CreateMyDriveRootEntry(root_resource_id);
594  grand_root_entry_map[util::kDriveOtherDirSpecialResourceId] =
595      util::CreateOtherDirEntry();
596  resource_metadata_->RefreshDirectoryOnUIThread(
597      directory_fetch_info,
598      grand_root_entry_map,
599      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh,
600                 weak_ptr_factory_.GetWeakPtr(),
601                 directory_fetch_info,
602                 callback));
603}
604
605void ChangeListLoader::DoLoadDirectoryFromServerAfterLoad(
606    const DirectoryFetchInfo& directory_fetch_info,
607    const FileOperationCallback& callback,
608    ScopedVector<ChangeList> change_lists,
609    FileError error) {
610  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
611  DCHECK(!callback.is_null());
612  DCHECK(!directory_fetch_info.empty());
613
614  if (error != FILE_ERROR_OK) {
615    LOG(ERROR) << "Failed to load directory: "
616               << directory_fetch_info.resource_id()
617               << ": " << FileErrorToString(error);
618    callback.Run(error);
619    return;
620  }
621
622  ChangeListProcessor::ResourceEntryMap entry_map;
623  ChangeListProcessor::ConvertToMap(change_lists.Pass(), &entry_map, NULL);
624  resource_metadata_->RefreshDirectoryOnUIThread(
625      directory_fetch_info,
626      entry_map,
627      base::Bind(&ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh,
628                 weak_ptr_factory_.GetWeakPtr(),
629                 directory_fetch_info,
630                 callback));
631}
632
633void ChangeListLoader::DoLoadDirectoryFromServerAfterRefresh(
634    const DirectoryFetchInfo& directory_fetch_info,
635    const FileOperationCallback& callback,
636    FileError error,
637    const base::FilePath& directory_path) {
638  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
639  DCHECK(!callback.is_null());
640
641  DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
642  callback.Run(error);
643  // Also notify the observers.
644  if (error == FILE_ERROR_OK) {
645    FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
646                      OnDirectoryChanged(directory_path));
647  }
648}
649
650void ChangeListLoader::UpdateFromChangeList(
651    scoped_ptr<google_apis::AboutResource> about_resource,
652    ScopedVector<ChangeList> change_lists,
653    bool is_delta_update,
654    const base::Closure& callback) {
655  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
656  DCHECK(!callback.is_null());
657
658  ChangeListProcessor* change_list_processor =
659      new ChangeListProcessor(resource_metadata_);
660  // Don't send directory content change notification while performing
661  // the initial content retrieval.
662  const bool should_notify_changed_directories = is_delta_update;
663
664  util::Log(logging::LOG_INFO,
665            "Apply change lists (is delta: %d)",
666            is_delta_update);
667  blocking_task_runner_->PostTaskAndReply(
668      FROM_HERE,
669      base::Bind(&ChangeListProcessor::Apply,
670                 base::Unretained(change_list_processor),
671                 base::Passed(&about_resource),
672                 base::Passed(&change_lists),
673                 is_delta_update),
674      base::Bind(&ChangeListLoader::UpdateFromChangeListAfterApply,
675                 weak_ptr_factory_.GetWeakPtr(),
676                 base::Owned(change_list_processor),
677                 should_notify_changed_directories,
678                 base::Time::Now(),
679                 callback));
680}
681
682void ChangeListLoader::UpdateFromChangeListAfterApply(
683    ChangeListProcessor* change_list_processor,
684    bool should_notify_changed_directories,
685    base::Time start_time,
686    const base::Closure& callback) {
687  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
688  DCHECK(change_list_processor);
689  DCHECK(!callback.is_null());
690
691  const base::TimeDelta elapsed = base::Time::Now() - start_time;
692  util::Log(logging::LOG_INFO,
693            "Change lists applied (elapsed time: %sms)",
694            base::Int64ToString(elapsed.InMilliseconds()).c_str());
695
696  if (should_notify_changed_directories) {
697    for (std::set<base::FilePath>::iterator dir_iter =
698            change_list_processor->changed_dirs().begin();
699        dir_iter != change_list_processor->changed_dirs().end();
700        ++dir_iter) {
701      FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
702                        OnDirectoryChanged(*dir_iter));
703    }
704  }
705
706  callback.Run();
707}
708
709}  // namespace internal
710}  // namespace drive
711