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