• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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/directory_loader.h"
6 
7 #include "base/callback.h"
8 #include "base/callback_helpers.h"
9 #include "base/metrics/histogram.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/time/time.h"
12 #include "chrome/browser/chromeos/drive/change_list_loader.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/resource_metadata.h"
18 #include "chrome/browser/drive/event_logger.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "google_apis/drive/drive_api_parser.h"
21 #include "url/gurl.h"
22 
23 using content::BrowserThread;
24 
25 namespace drive {
26 namespace internal {
27 
28 namespace {
29 
30 // Minimum changestamp gap required to start loading directory.
31 const int kMinimumChangestampGap = 50;
32 
CheckLocalState(ResourceMetadata * resource_metadata,const google_apis::AboutResource & about_resource,const std::string & local_id,ResourceEntry * entry,int64 * local_changestamp)33 FileError CheckLocalState(ResourceMetadata* resource_metadata,
34                           const google_apis::AboutResource& about_resource,
35                           const std::string& local_id,
36                           ResourceEntry* entry,
37                           int64* local_changestamp) {
38   // Fill My Drive resource ID.
39   ResourceEntry mydrive;
40   FileError error = resource_metadata->GetResourceEntryByPath(
41       util::GetDriveMyDriveRootPath(), &mydrive);
42   if (error != FILE_ERROR_OK)
43     return error;
44 
45   if (mydrive.resource_id().empty()) {
46     mydrive.set_resource_id(about_resource.root_folder_id());
47     error = resource_metadata->RefreshEntry(mydrive);
48     if (error != FILE_ERROR_OK)
49       return error;
50   }
51 
52   // Get entry.
53   error = resource_metadata->GetResourceEntryById(local_id, entry);
54   if (error != FILE_ERROR_OK)
55     return error;
56 
57   // Get the local changestamp.
58   return resource_metadata->GetLargestChangestamp(local_changestamp);
59 }
60 
UpdateChangestamp(ResourceMetadata * resource_metadata,const DirectoryFetchInfo & directory_fetch_info,base::FilePath * directory_path)61 FileError UpdateChangestamp(ResourceMetadata* resource_metadata,
62                             const DirectoryFetchInfo& directory_fetch_info,
63                             base::FilePath* directory_path) {
64   // Update the directory changestamp.
65   ResourceEntry directory;
66   FileError error = resource_metadata->GetResourceEntryById(
67       directory_fetch_info.local_id(), &directory);
68   if (error != FILE_ERROR_OK)
69     return error;
70 
71   if (!directory.file_info().is_directory())
72     return FILE_ERROR_NOT_A_DIRECTORY;
73 
74   directory.mutable_directory_specific_info()->set_changestamp(
75       directory_fetch_info.changestamp());
76   error = resource_metadata->RefreshEntry(directory);
77   if (error != FILE_ERROR_OK)
78     return error;
79 
80   // Get the directory path.
81   return resource_metadata->GetFilePath(directory_fetch_info.local_id(),
82                                         directory_path);
83 }
84 
85 }  // namespace
86 
87 struct DirectoryLoader::ReadDirectoryCallbackState {
88   ReadDirectoryEntriesCallback entries_callback;
89   FileOperationCallback completion_callback;
90   std::set<std::string> sent_entry_names;
91 };
92 
93 // Fetches the resource entries in the directory with |directory_resource_id|.
94 class DirectoryLoader::FeedFetcher {
95  public:
FeedFetcher(DirectoryLoader * loader,const DirectoryFetchInfo & directory_fetch_info,const std::string & root_folder_id)96   FeedFetcher(DirectoryLoader* loader,
97               const DirectoryFetchInfo& directory_fetch_info,
98               const std::string& root_folder_id)
99       : loader_(loader),
100         directory_fetch_info_(directory_fetch_info),
101         root_folder_id_(root_folder_id),
102         weak_ptr_factory_(this) {
103   }
104 
~FeedFetcher()105   ~FeedFetcher() {
106   }
107 
Run(const FileOperationCallback & callback)108   void Run(const FileOperationCallback& callback) {
109     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
110     DCHECK(!callback.is_null());
111     DCHECK(!directory_fetch_info_.resource_id().empty());
112 
113     // Remember the time stamp for usage stats.
114     start_time_ = base::TimeTicks::Now();
115 
116     loader_->scheduler_->GetFileListInDirectory(
117         directory_fetch_info_.resource_id(),
118         base::Bind(&FeedFetcher::OnFileListFetched,
119                    weak_ptr_factory_.GetWeakPtr(), callback));
120   }
121 
122  private:
OnFileListFetched(const FileOperationCallback & callback,google_apis::GDataErrorCode status,scoped_ptr<google_apis::FileList> file_list)123   void OnFileListFetched(const FileOperationCallback& callback,
124                          google_apis::GDataErrorCode status,
125                          scoped_ptr<google_apis::FileList> file_list) {
126     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127     DCHECK(!callback.is_null());
128 
129     FileError error = GDataToFileError(status);
130     if (error != FILE_ERROR_OK) {
131       callback.Run(error);
132       return;
133     }
134 
135     DCHECK(file_list);
136     scoped_ptr<ChangeList> change_list(new ChangeList(*file_list));
137     GURL next_url = file_list->next_link();
138 
139     ResourceEntryVector* entries = new ResourceEntryVector;
140     loader_->loader_controller_->ScheduleRun(base::Bind(
141         base::IgnoreResult(
142             &base::PostTaskAndReplyWithResult<FileError, FileError>),
143         loader_->blocking_task_runner_,
144         FROM_HERE,
145         base::Bind(&ChangeListProcessor::RefreshDirectory,
146                    loader_->resource_metadata_,
147                    directory_fetch_info_,
148                    base::Passed(&change_list),
149                    entries),
150         base::Bind(&FeedFetcher::OnDirectoryRefreshed,
151                    weak_ptr_factory_.GetWeakPtr(),
152                    callback,
153                    next_url,
154                    base::Owned(entries))));
155   }
156 
OnDirectoryRefreshed(const FileOperationCallback & callback,const GURL & next_url,const std::vector<ResourceEntry> * refreshed_entries,FileError error)157   void OnDirectoryRefreshed(
158       const FileOperationCallback& callback,
159       const GURL& next_url,
160       const std::vector<ResourceEntry>* refreshed_entries,
161       FileError error) {
162     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
163     DCHECK(!callback.is_null());
164 
165     if (error != FILE_ERROR_OK) {
166       callback.Run(error);
167       return;
168     }
169 
170     loader_->SendEntries(directory_fetch_info_.local_id(), *refreshed_entries);
171 
172     if (!next_url.is_empty()) {
173       // There is the remaining result so fetch it.
174       loader_->scheduler_->GetRemainingFileList(
175           next_url,
176           base::Bind(&FeedFetcher::OnFileListFetched,
177                      weak_ptr_factory_.GetWeakPtr(), callback));
178       return;
179     }
180 
181     UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime",
182                         base::TimeTicks::Now() - start_time_);
183 
184     // Note: The fetcher is managed by DirectoryLoader, and the instance
185     // will be deleted in the callback. Do not touch the fields after this
186     // invocation.
187     callback.Run(FILE_ERROR_OK);
188   }
189 
190   DirectoryLoader* loader_;
191   DirectoryFetchInfo directory_fetch_info_;
192   std::string root_folder_id_;
193   base::TimeTicks start_time_;
194   base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_;
195   DISALLOW_COPY_AND_ASSIGN(FeedFetcher);
196 };
197 
DirectoryLoader(EventLogger * logger,base::SequencedTaskRunner * blocking_task_runner,ResourceMetadata * resource_metadata,JobScheduler * scheduler,AboutResourceLoader * about_resource_loader,LoaderController * loader_controller)198 DirectoryLoader::DirectoryLoader(
199     EventLogger* logger,
200     base::SequencedTaskRunner* blocking_task_runner,
201     ResourceMetadata* resource_metadata,
202     JobScheduler* scheduler,
203     AboutResourceLoader* about_resource_loader,
204     LoaderController* loader_controller)
205     : logger_(logger),
206       blocking_task_runner_(blocking_task_runner),
207       resource_metadata_(resource_metadata),
208       scheduler_(scheduler),
209       about_resource_loader_(about_resource_loader),
210       loader_controller_(loader_controller),
211       weak_ptr_factory_(this) {
212 }
213 
~DirectoryLoader()214 DirectoryLoader::~DirectoryLoader() {
215   STLDeleteElements(&fast_fetch_feed_fetcher_set_);
216 }
217 
AddObserver(ChangeListLoaderObserver * observer)218 void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) {
219   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
220   observers_.AddObserver(observer);
221 }
222 
RemoveObserver(ChangeListLoaderObserver * observer)223 void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
224   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
225   observers_.RemoveObserver(observer);
226 }
227 
ReadDirectory(const base::FilePath & directory_path,const ReadDirectoryEntriesCallback & entries_callback,const FileOperationCallback & completion_callback)228 void DirectoryLoader::ReadDirectory(
229     const base::FilePath& directory_path,
230     const ReadDirectoryEntriesCallback& entries_callback,
231     const FileOperationCallback& completion_callback) {
232   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
233   DCHECK(!completion_callback.is_null());
234 
235   ResourceEntry* entry = new ResourceEntry;
236   base::PostTaskAndReplyWithResult(
237       blocking_task_runner_.get(),
238       FROM_HERE,
239       base::Bind(&ResourceMetadata::GetResourceEntryByPath,
240                  base::Unretained(resource_metadata_),
241                  directory_path,
242                  entry),
243       base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
244                  weak_ptr_factory_.GetWeakPtr(),
245                  directory_path,
246                  entries_callback,
247                  completion_callback,
248                  true,  // should_try_loading_parent
249                  base::Owned(entry)));
250 }
251 
ReadDirectoryAfterGetEntry(const base::FilePath & directory_path,const ReadDirectoryEntriesCallback & entries_callback,const FileOperationCallback & completion_callback,bool should_try_loading_parent,const ResourceEntry * entry,FileError error)252 void DirectoryLoader::ReadDirectoryAfterGetEntry(
253     const base::FilePath& directory_path,
254     const ReadDirectoryEntriesCallback& entries_callback,
255     const FileOperationCallback& completion_callback,
256     bool should_try_loading_parent,
257     const ResourceEntry* entry,
258     FileError error) {
259   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
260   DCHECK(!completion_callback.is_null());
261 
262   if (error == FILE_ERROR_NOT_FOUND &&
263       should_try_loading_parent &&
264       util::GetDriveGrandRootPath().IsParent(directory_path)) {
265     // This entry may be found after loading the parent.
266     ReadDirectory(directory_path.DirName(),
267                   ReadDirectoryEntriesCallback(),
268                   base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent,
269                              weak_ptr_factory_.GetWeakPtr(),
270                              directory_path,
271                              entries_callback,
272                              completion_callback));
273     return;
274   }
275   if (error != FILE_ERROR_OK) {
276     completion_callback.Run(error);
277     return;
278   }
279 
280   if (!entry->file_info().is_directory()) {
281     completion_callback.Run(FILE_ERROR_NOT_A_DIRECTORY);
282     return;
283   }
284 
285   DirectoryFetchInfo directory_fetch_info(
286       entry->local_id(),
287       entry->resource_id(),
288       entry->directory_specific_info().changestamp());
289 
290   // Register the callback function to be called when it is loaded.
291   const std::string& local_id = directory_fetch_info.local_id();
292   ReadDirectoryCallbackState callback_state;
293   callback_state.entries_callback = entries_callback;
294   callback_state.completion_callback = completion_callback;
295   pending_load_callback_[local_id].push_back(callback_state);
296 
297   // If loading task for |local_id| is already running, do nothing.
298   if (pending_load_callback_[local_id].size() > 1)
299     return;
300 
301   // Note: To be precise, we need to call UpdateAboutResource() here. However,
302   // - It is costly to do GetAboutResource HTTP request every time.
303   // - The chance using an old value is small; it only happens when
304   //   ReadDirectory is called during one GetAboutResource roundtrip time
305   //   of a change list fetching.
306   // - Even if the value is old, it just marks the directory as older. It may
307   //   trigger one future unnecessary re-fetch, but it'll never lose data.
308   about_resource_loader_->GetAboutResource(
309       base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource,
310                  weak_ptr_factory_.GetWeakPtr(), local_id));
311 }
312 
ReadDirectoryAfterLoadParent(const base::FilePath & directory_path,const ReadDirectoryEntriesCallback & entries_callback,const FileOperationCallback & completion_callback,FileError error)313 void DirectoryLoader::ReadDirectoryAfterLoadParent(
314     const base::FilePath& directory_path,
315     const ReadDirectoryEntriesCallback& entries_callback,
316     const FileOperationCallback& completion_callback,
317     FileError error) {
318   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
319   DCHECK(!completion_callback.is_null());
320 
321   if (error != FILE_ERROR_OK) {
322     completion_callback.Run(error);
323     return;
324   }
325 
326   ResourceEntry* entry = new ResourceEntry;
327   base::PostTaskAndReplyWithResult(
328       blocking_task_runner_.get(),
329       FROM_HERE,
330       base::Bind(&ResourceMetadata::GetResourceEntryByPath,
331                  base::Unretained(resource_metadata_),
332                  directory_path,
333                  entry),
334       base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
335                  weak_ptr_factory_.GetWeakPtr(),
336                  directory_path,
337                  entries_callback,
338                  completion_callback,
339                  false,  // should_try_loading_parent
340                  base::Owned(entry)));
341 }
342 
ReadDirectoryAfterGetAboutResource(const std::string & local_id,google_apis::GDataErrorCode status,scoped_ptr<google_apis::AboutResource> about_resource)343 void DirectoryLoader::ReadDirectoryAfterGetAboutResource(
344     const std::string& local_id,
345     google_apis::GDataErrorCode status,
346     scoped_ptr<google_apis::AboutResource> about_resource) {
347   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
348 
349   FileError error = GDataToFileError(status);
350   if (error != FILE_ERROR_OK) {
351     OnDirectoryLoadComplete(local_id, error);
352     return;
353   }
354 
355   DCHECK(about_resource);
356 
357   // Check the current status of local metadata, and start loading if needed.
358   google_apis::AboutResource* about_resource_ptr = about_resource.get();
359   ResourceEntry* entry = new ResourceEntry;
360   int64* local_changestamp = new int64;
361   base::PostTaskAndReplyWithResult(
362       blocking_task_runner_,
363       FROM_HERE,
364       base::Bind(&CheckLocalState,
365                  resource_metadata_,
366                  *about_resource_ptr,
367                  local_id,
368                  entry,
369                  local_changestamp),
370       base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState,
371                  weak_ptr_factory_.GetWeakPtr(),
372                  base::Passed(&about_resource),
373                  local_id,
374                  base::Owned(entry),
375                  base::Owned(local_changestamp)));
376 }
377 
ReadDirectoryAfterCheckLocalState(scoped_ptr<google_apis::AboutResource> about_resource,const std::string & local_id,const ResourceEntry * entry,const int64 * local_changestamp,FileError error)378 void DirectoryLoader::ReadDirectoryAfterCheckLocalState(
379     scoped_ptr<google_apis::AboutResource> about_resource,
380     const std::string& local_id,
381     const ResourceEntry* entry,
382     const int64* local_changestamp,
383     FileError error) {
384   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
385   DCHECK(about_resource);
386 
387   if (error != FILE_ERROR_OK) {
388     OnDirectoryLoadComplete(local_id, error);
389     return;
390   }
391   // This entry does not exist on the server.
392   if (entry->resource_id().empty()) {
393     OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
394     return;
395   }
396 
397   int64 remote_changestamp = about_resource->largest_change_id();
398 
399   // Start loading the directory.
400   int64 directory_changestamp = std::max(
401       entry->directory_specific_info().changestamp(), *local_changestamp);
402 
403   DirectoryFetchInfo directory_fetch_info(
404       local_id, entry->resource_id(), remote_changestamp);
405 
406   // If the directory's changestamp is new enough, just schedule to run the
407   // callback, as there is no need to fetch the directory.
408   if (directory_changestamp + kMinimumChangestampGap > remote_changestamp) {
409     OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
410   } else {
411     // Start fetching the directory content, and mark it with the changestamp
412     // |remote_changestamp|.
413     LoadDirectoryFromServer(directory_fetch_info);
414   }
415 }
416 
OnDirectoryLoadComplete(const std::string & local_id,FileError error)417 void DirectoryLoader::OnDirectoryLoadComplete(const std::string& local_id,
418                                               FileError error) {
419   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
420 
421   LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
422   if (it == pending_load_callback_.end())
423     return;
424 
425   // No need to read metadata when no one needs entries.
426   bool needs_to_send_entries = false;
427   for (size_t i = 0; i < it->second.size(); ++i) {
428     const ReadDirectoryCallbackState& callback_state = it->second[i];
429     if (!callback_state.entries_callback.is_null())
430       needs_to_send_entries = true;
431   }
432 
433   if (!needs_to_send_entries) {
434     OnDirectoryLoadCompleteAfterRead(local_id, NULL, FILE_ERROR_OK);
435     return;
436   }
437 
438   ResourceEntryVector* entries = new ResourceEntryVector;
439   base::PostTaskAndReplyWithResult(
440       blocking_task_runner_.get(),
441       FROM_HERE,
442       base::Bind(&ResourceMetadata::ReadDirectoryById,
443                  base::Unretained(resource_metadata_), local_id, entries),
444       base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead,
445                  weak_ptr_factory_.GetWeakPtr(),
446                  local_id,
447                  base::Owned(entries)));
448 }
449 
OnDirectoryLoadCompleteAfterRead(const std::string & local_id,const ResourceEntryVector * entries,FileError error)450 void DirectoryLoader::OnDirectoryLoadCompleteAfterRead(
451     const std::string& local_id,
452     const ResourceEntryVector* entries,
453     FileError error) {
454   LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
455   if (it != pending_load_callback_.end()) {
456     DVLOG(1) << "Running callback for " << local_id;
457 
458     if (error == FILE_ERROR_OK && entries)
459       SendEntries(local_id, *entries);
460 
461     for (size_t i = 0; i < it->second.size(); ++i) {
462       const ReadDirectoryCallbackState& callback_state = it->second[i];
463       callback_state.completion_callback.Run(error);
464     }
465     pending_load_callback_.erase(it);
466   }
467 }
468 
SendEntries(const std::string & local_id,const ResourceEntryVector & entries)469 void DirectoryLoader::SendEntries(const std::string& local_id,
470                                   const ResourceEntryVector& entries) {
471   LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
472   DCHECK(it != pending_load_callback_.end());
473 
474   for (size_t i = 0; i < it->second.size(); ++i) {
475     ReadDirectoryCallbackState* callback_state = &it->second[i];
476     if (callback_state->entries_callback.is_null())
477       continue;
478 
479     // Filter out entries which were already sent.
480     scoped_ptr<ResourceEntryVector> entries_to_send(new ResourceEntryVector);
481     for (size_t i = 0; i < entries.size(); ++i) {
482       const ResourceEntry& entry = entries[i];
483       if (!callback_state->sent_entry_names.count(entry.base_name())) {
484         callback_state->sent_entry_names.insert(entry.base_name());
485         entries_to_send->push_back(entry);
486       }
487     }
488     callback_state->entries_callback.Run(entries_to_send.Pass());
489   }
490 }
491 
LoadDirectoryFromServer(const DirectoryFetchInfo & directory_fetch_info)492 void DirectoryLoader::LoadDirectoryFromServer(
493     const DirectoryFetchInfo& directory_fetch_info) {
494   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
495   DCHECK(!directory_fetch_info.empty());
496   DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
497 
498   const google_apis::AboutResource* about_resource =
499       about_resource_loader_->cached_about_resource();
500   DCHECK(about_resource);
501 
502   logger_->Log(logging::LOG_INFO,
503                "Fast-fetch start: %s; Server changestamp: %s",
504                directory_fetch_info.ToString().c_str(),
505                base::Int64ToString(
506                    about_resource->largest_change_id()).c_str());
507 
508   FeedFetcher* fetcher = new FeedFetcher(this,
509                                          directory_fetch_info,
510                                          about_resource->root_folder_id());
511   fast_fetch_feed_fetcher_set_.insert(fetcher);
512   fetcher->Run(
513       base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad,
514                  weak_ptr_factory_.GetWeakPtr(),
515                  directory_fetch_info,
516                  fetcher));
517 }
518 
LoadDirectoryFromServerAfterLoad(const DirectoryFetchInfo & directory_fetch_info,FeedFetcher * fetcher,FileError error)519 void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
520     const DirectoryFetchInfo& directory_fetch_info,
521     FeedFetcher* fetcher,
522     FileError error) {
523   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
524   DCHECK(!directory_fetch_info.empty());
525 
526   // Delete the fetcher.
527   fast_fetch_feed_fetcher_set_.erase(fetcher);
528   delete fetcher;
529 
530   logger_->Log(logging::LOG_INFO,
531                "Fast-fetch complete: %s => %s",
532                directory_fetch_info.ToString().c_str(),
533                FileErrorToString(error).c_str());
534 
535   if (error != FILE_ERROR_OK) {
536     LOG(ERROR) << "Failed to load directory: "
537                << directory_fetch_info.local_id()
538                << ": " << FileErrorToString(error);
539     OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
540     return;
541   }
542 
543   // Update changestamp and get the directory path.
544   base::FilePath* directory_path = new base::FilePath;
545   base::PostTaskAndReplyWithResult(
546       blocking_task_runner_.get(),
547       FROM_HERE,
548       base::Bind(&UpdateChangestamp,
549                  resource_metadata_,
550                  directory_fetch_info,
551                  directory_path),
552       base::Bind(
553           &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp,
554           weak_ptr_factory_.GetWeakPtr(),
555           directory_fetch_info,
556           base::Owned(directory_path)));
557 }
558 
LoadDirectoryFromServerAfterUpdateChangestamp(const DirectoryFetchInfo & directory_fetch_info,const base::FilePath * directory_path,FileError error)559 void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp(
560     const DirectoryFetchInfo& directory_fetch_info,
561     const base::FilePath* directory_path,
562     FileError error) {
563   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
564 
565   DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
566   OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
567 
568   // Also notify the observers.
569   if (error == FILE_ERROR_OK && !directory_path->empty()) {
570     FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
571                       OnDirectoryChanged(*directory_path));
572   }
573 }
574 
575 }  // namespace internal
576 }  // namespace drive
577