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