directory_loader.cc revision 23730a6e56a168d1879203e4b3819bb36e3d8f1f
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/drive_api_util.h"
19#include "chrome/browser/drive/drive_service_interface.h"
20#include "chrome/browser/drive/event_logger.h"
21#include "content/public/browser/browser_thread.h"
22#include "google_apis/drive/drive_api_parser.h"
23#include "url/gurl.h"
24
25using content::BrowserThread;
26
27namespace drive {
28namespace internal {
29
30namespace {
31
32// Minimum changestamp gap required to start loading directory.
33const int kMinimumChangestampGap = 50;
34
35FileError CheckLocalState(ResourceMetadata* resource_metadata,
36                          const google_apis::AboutResource& about_resource,
37                          const std::string& local_id,
38                          ResourceEntry* entry,
39                          int64* local_changestamp) {
40  // Fill My Drive resource ID.
41  ResourceEntry mydrive;
42  FileError error = resource_metadata->GetResourceEntryByPath(
43      util::GetDriveMyDriveRootPath(), &mydrive);
44  if (error != FILE_ERROR_OK)
45    return error;
46
47  if (mydrive.resource_id().empty()) {
48    mydrive.set_resource_id(about_resource.root_folder_id());
49    error = resource_metadata->RefreshEntry(mydrive);
50    if (error != FILE_ERROR_OK)
51      return error;
52  }
53
54  // Get entry.
55  error = resource_metadata->GetResourceEntryById(local_id, entry);
56  if (error != FILE_ERROR_OK)
57    return error;
58
59  // Get the local changestamp.
60  *local_changestamp = resource_metadata->GetLargestChangestamp();
61  return FILE_ERROR_OK;
62}
63
64void ReadDirectoryAfterRead(const std::vector<ReadDirectoryCallback>& callbacks,
65                            scoped_ptr<ResourceEntryVector> entries,
66                            FileError error) {
67  if (error != FILE_ERROR_OK)
68    entries.reset();
69  for (size_t i = 0; i < callbacks.size(); ++i) {
70    scoped_ptr<ResourceEntryVector> copied_entries;
71    if (entries)
72      copied_entries.reset(new ResourceEntryVector(*entries));
73
74    callbacks[i].Run(error, copied_entries.Pass(), false /*has_more*/);
75  }
76}
77
78}  // namespace
79
80// Fetches the resource entries in the directory with |directory_resource_id|.
81class DirectoryLoader::FeedFetcher {
82 public:
83  typedef base::Callback<void(FileError, ScopedVector<ChangeList>)>
84      FeedFetcherCallback;
85
86  FeedFetcher(JobScheduler* scheduler,
87              DriveServiceInterface* drive_service,
88              const std::string& directory_resource_id,
89              const std::string& root_folder_id)
90      : scheduler_(scheduler),
91        drive_service_(drive_service),
92        directory_resource_id_(directory_resource_id),
93        root_folder_id_(root_folder_id),
94        weak_ptr_factory_(this) {
95  }
96
97  ~FeedFetcher() {
98  }
99
100  void Run(const FeedFetcherCallback& callback) {
101    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
102    DCHECK(!callback.is_null());
103    DCHECK(!directory_resource_id_.empty());
104
105    // Remember the time stamp for usage stats.
106    start_time_ = base::TimeTicks::Now();
107
108    // We use WAPI's GetResourceListInDirectory even if Drive API v2 is
109    // enabled. This is the short term work around of the performance
110    // regression.
111    // TODO(hashimoto): Remove this. crbug.com/340931.
112
113    std::string resource_id = directory_resource_id_;
114    if (directory_resource_id_ == root_folder_id_) {
115      // GData WAPI doesn't accept the root directory id which is used in Drive
116      // API v2. So it is necessary to translate it here.
117      resource_id = util::kWapiRootDirectoryResourceId;
118    }
119
120    scheduler_->GetResourceListInDirectoryByWapi(
121        resource_id,
122        base::Bind(&FeedFetcher::OnResourceListFetched,
123                   weak_ptr_factory_.GetWeakPtr(), callback));
124  }
125
126 private:
127  void OnResourceListFetched(
128      const FeedFetcherCallback& callback,
129      google_apis::GDataErrorCode status,
130      scoped_ptr<google_apis::ResourceList> resource_list) {
131    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
132    DCHECK(!callback.is_null());
133
134    FileError error = GDataToFileError(status);
135    if (error != FILE_ERROR_OK) {
136      callback.Run(error, ScopedVector<ChangeList>());
137      return;
138    }
139
140    // Add the current change list to the list of collected lists.
141    DCHECK(resource_list);
142    ChangeList* change_list = new ChangeList(*resource_list);
143    FixResourceIdInChangeList(change_list);
144    change_lists_.push_back(change_list);
145
146    GURL next_url;
147    if (resource_list->GetNextFeedURL(&next_url) && !next_url.is_empty()) {
148      // There is the remaining result so fetch it.
149      scheduler_->GetRemainingResourceList(
150          next_url,
151          base::Bind(&FeedFetcher::OnResourceListFetched,
152                     weak_ptr_factory_.GetWeakPtr(), callback));
153      return;
154    }
155
156    UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime",
157                        base::TimeTicks::Now() - start_time_);
158
159    // Note: The fetcher is managed by DirectoryLoader, and the instance
160    // will be deleted in the callback. Do not touch the fields after this
161    // invocation.
162    callback.Run(FILE_ERROR_OK, change_lists_.Pass());
163  }
164
165  // Fixes resource IDs in |change_list| into the format that |drive_service_|
166  // can understand. Note that |change_list| contains IDs in GData WAPI format
167  // since currently we always use WAPI for fast fetch, regardless of the flag.
168  void FixResourceIdInChangeList(ChangeList* change_list) {
169    std::vector<ResourceEntry>* entries = change_list->mutable_entries();
170    std::vector<std::string>* parent_resource_ids =
171        change_list->mutable_parent_resource_ids();
172    for (size_t i = 0; i < entries->size(); ++i) {
173      ResourceEntry* entry = &(*entries)[i];
174      if (entry->has_resource_id())
175        entry->set_resource_id(FixResourceId(entry->resource_id()));
176
177      (*parent_resource_ids)[i] = FixResourceId((*parent_resource_ids)[i]);
178    }
179  }
180
181  std::string FixResourceId(const std::string& resource_id) {
182    if (resource_id == util::kWapiRootDirectoryResourceId)
183      return root_folder_id_;
184    return drive_service_->GetResourceIdCanonicalizer().Run(resource_id);
185  }
186
187  JobScheduler* scheduler_;
188  DriveServiceInterface* drive_service_;
189  std::string directory_resource_id_;
190  std::string root_folder_id_;
191  ScopedVector<ChangeList> change_lists_;
192  base::TimeTicks start_time_;
193  base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_;
194  DISALLOW_COPY_AND_ASSIGN(FeedFetcher);
195};
196
197DirectoryLoader::DirectoryLoader(
198    EventLogger* logger,
199    base::SequencedTaskRunner* blocking_task_runner,
200    ResourceMetadata* resource_metadata,
201    JobScheduler* scheduler,
202    DriveServiceInterface* drive_service,
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      drive_service_(drive_service),
210      about_resource_loader_(about_resource_loader),
211      loader_controller_(loader_controller),
212      weak_ptr_factory_(this) {
213}
214
215DirectoryLoader::~DirectoryLoader() {
216  STLDeleteElements(&fast_fetch_feed_fetcher_set_);
217}
218
219void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) {
220  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
221  observers_.AddObserver(observer);
222}
223
224void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
225  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
226  observers_.RemoveObserver(observer);
227}
228
229void DirectoryLoader::ReadDirectory(const base::FilePath& directory_path,
230                                    const ReadDirectoryCallback& callback) {
231  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
232  DCHECK(!callback.is_null());
233
234  ResourceEntry* entry = new ResourceEntry;
235  base::PostTaskAndReplyWithResult(
236      blocking_task_runner_.get(),
237      FROM_HERE,
238      base::Bind(&ResourceMetadata::GetResourceEntryByPath,
239                 base::Unretained(resource_metadata_),
240                 directory_path,
241                 entry),
242      base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
243                 weak_ptr_factory_.GetWeakPtr(),
244                 directory_path,
245                 callback,
246                 true,  // should_try_loading_parent
247                 base::Owned(entry)));
248}
249
250void DirectoryLoader::ReadDirectoryAfterGetEntry(
251    const base::FilePath& directory_path,
252    const ReadDirectoryCallback& callback,
253    bool should_try_loading_parent,
254    const ResourceEntry* entry,
255    FileError error) {
256  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
257  DCHECK(!callback.is_null());
258
259  if (error == FILE_ERROR_NOT_FOUND &&
260      should_try_loading_parent &&
261      util::GetDriveGrandRootPath().IsParent(directory_path)) {
262    // This entry may be found after loading the parent.
263    ReadDirectory(directory_path.DirName(),
264                  base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent,
265                             weak_ptr_factory_.GetWeakPtr(),
266                             directory_path,
267                             callback));
268    return;
269  }
270  if (error != FILE_ERROR_OK) {
271    callback.Run(error, scoped_ptr<ResourceEntryVector>(), false /*has_more*/);
272    return;
273  }
274
275  if (!entry->file_info().is_directory()) {
276    callback.Run(FILE_ERROR_NOT_A_DIRECTORY,
277                 scoped_ptr<ResourceEntryVector>(), false /*has_more*/);
278    return;
279  }
280
281  DirectoryFetchInfo directory_fetch_info(
282      entry->local_id(),
283      entry->resource_id(),
284      entry->directory_specific_info().changestamp());
285
286  // Register the callback function to be called when it is loaded.
287  const std::string& local_id = directory_fetch_info.local_id();
288  pending_load_callback_[local_id].push_back(callback);
289
290  // If loading task for |local_id| is already running, do nothing.
291  if (pending_load_callback_[local_id].size() > 1)
292    return;
293
294  // Note: To be precise, we need to call UpdateAboutResource() here. However,
295  // - It is costly to do GetAboutResource HTTP request every time.
296  // - The chance using an old value is small; it only happens when
297  //   ReadDirectory is called during one GetAboutResource roundtrip time
298  //   of a change list fetching.
299  // - Even if the value is old, it just marks the directory as older. It may
300  //   trigger one future unnecessary re-fetch, but it'll never lose data.
301  about_resource_loader_->GetAboutResource(
302      base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource,
303                 weak_ptr_factory_.GetWeakPtr(), local_id));
304}
305
306void DirectoryLoader::ReadDirectoryAfterLoadParent(
307    const base::FilePath& directory_path,
308    const ReadDirectoryCallback& callback,
309    FileError error,
310    scoped_ptr<ResourceEntryVector> entries,
311    bool has_more) {
312  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
313  DCHECK(!callback.is_null());
314
315  if (has_more)
316    return;
317
318  if (error != FILE_ERROR_OK) {
319    callback.Run(error, scoped_ptr<ResourceEntryVector>(), false /*has_more*/);
320    return;
321  }
322
323  ResourceEntry* entry = new ResourceEntry;
324  base::PostTaskAndReplyWithResult(
325      blocking_task_runner_.get(),
326      FROM_HERE,
327      base::Bind(&ResourceMetadata::GetResourceEntryByPath,
328                 base::Unretained(resource_metadata_),
329                 directory_path,
330                 entry),
331      base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
332                 weak_ptr_factory_.GetWeakPtr(),
333                 directory_path,
334                 callback,
335                 false,  // should_try_loading_parent
336                 base::Owned(entry)));
337}
338
339void DirectoryLoader::ReadDirectoryAfterGetAboutResource(
340    const std::string& local_id,
341    google_apis::GDataErrorCode status,
342    scoped_ptr<google_apis::AboutResource> about_resource) {
343  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
344
345  FileError error = GDataToFileError(status);
346  if (error != FILE_ERROR_OK) {
347    OnDirectoryLoadComplete(local_id, error);
348    return;
349  }
350
351  DCHECK(about_resource);
352
353  // Check the current status of local metadata, and start loading if needed.
354  google_apis::AboutResource* about_resource_ptr = about_resource.get();
355  ResourceEntry* entry = new ResourceEntry;
356  int64* local_changestamp = new int64;
357  base::PostTaskAndReplyWithResult(
358      blocking_task_runner_,
359      FROM_HERE,
360      base::Bind(&CheckLocalState,
361                 resource_metadata_,
362                 *about_resource_ptr,
363                 local_id,
364                 entry,
365                 local_changestamp),
366      base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState,
367                 weak_ptr_factory_.GetWeakPtr(),
368                 base::Passed(&about_resource),
369                 local_id,
370                 base::Owned(entry),
371                 base::Owned(local_changestamp)));
372}
373
374void DirectoryLoader::ReadDirectoryAfterCheckLocalState(
375    scoped_ptr<google_apis::AboutResource> about_resource,
376    const std::string& local_id,
377    const ResourceEntry* entry,
378    const int64* local_changestamp,
379    FileError error) {
380  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
381  DCHECK(about_resource);
382
383  if (error != FILE_ERROR_OK) {
384    OnDirectoryLoadComplete(local_id, error);
385    return;
386  }
387  // This entry does not exist on the server.
388  if (entry->resource_id().empty()) {
389    OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
390    return;
391  }
392
393  int64 remote_changestamp = about_resource->largest_change_id();
394
395  // Start loading the directory.
396  int64 directory_changestamp = std::max(
397      entry->directory_specific_info().changestamp(), *local_changestamp);
398
399  DirectoryFetchInfo directory_fetch_info(
400      local_id, entry->resource_id(), remote_changestamp);
401
402  // We may not fetch from the server at all if the local metadata is new
403  // enough, but we log this message here, so "Fast-fetch start" and
404  // "Fast-fetch complete" always match.
405  // TODO(satorux): Distinguish the "not fetching at all" case.
406  logger_->Log(logging::LOG_INFO,
407               "Fast-fetch start: %s; Server changestamp: %s",
408               directory_fetch_info.ToString().c_str(),
409               base::Int64ToString(remote_changestamp).c_str());
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  logger_->Log(logging::LOG_INFO,
427               "Fast-fetch complete: %s => %s",
428               local_id.c_str(),
429               FileErrorToString(error).c_str());
430  LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
431  if (it != pending_load_callback_.end()) {
432    DVLOG(1) << "Running callback for " << local_id;
433    const std::vector<ReadDirectoryCallback>& callbacks = it->second;
434    ResourceEntryVector* entries = new ResourceEntryVector;
435    base::PostTaskAndReplyWithResult(
436        blocking_task_runner_.get(),
437        FROM_HERE,
438        base::Bind(&ResourceMetadata::ReadDirectoryById,
439                   base::Unretained(resource_metadata_), local_id, entries),
440        base::Bind(&ReadDirectoryAfterRead, callbacks,
441                   base::Passed(scoped_ptr<ResourceEntryVector>(entries))));
442    pending_load_callback_.erase(it);
443  }
444}
445
446void DirectoryLoader::LoadDirectoryFromServer(
447    const DirectoryFetchInfo& directory_fetch_info) {
448  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
449  DCHECK(!directory_fetch_info.empty());
450  DCHECK(about_resource_loader_->cached_about_resource());
451  DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
452
453  FeedFetcher* fetcher = new FeedFetcher(
454      scheduler_,
455      drive_service_,
456      directory_fetch_info.resource_id(),
457      about_resource_loader_->cached_about_resource()->root_folder_id());
458  fast_fetch_feed_fetcher_set_.insert(fetcher);
459  fetcher->Run(
460      base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad,
461                 weak_ptr_factory_.GetWeakPtr(),
462                 directory_fetch_info,
463                 fetcher));
464}
465
466void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
467    const DirectoryFetchInfo& directory_fetch_info,
468    FeedFetcher* fetcher,
469    FileError error,
470    ScopedVector<ChangeList> change_lists) {
471  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
472  DCHECK(!directory_fetch_info.empty());
473
474  // Delete the fetcher.
475  fast_fetch_feed_fetcher_set_.erase(fetcher);
476  delete fetcher;
477
478  if (error != FILE_ERROR_OK) {
479    LOG(ERROR) << "Failed to load directory: "
480               << directory_fetch_info.local_id()
481               << ": " << FileErrorToString(error);
482    OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
483    return;
484  }
485
486  base::FilePath* directory_path = new base::FilePath;
487  loader_controller_->ScheduleRun(base::Bind(
488      base::IgnoreResult(
489          &base::PostTaskAndReplyWithResult<FileError, FileError>),
490      blocking_task_runner_,
491      FROM_HERE,
492      base::Bind(&ChangeListProcessor::RefreshDirectory,
493                 resource_metadata_,
494                 directory_fetch_info,
495                 base::Passed(&change_lists),
496                 directory_path),
497      base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterRefresh,
498                 weak_ptr_factory_.GetWeakPtr(),
499                 directory_fetch_info,
500                 base::Owned(directory_path))));
501}
502
503void DirectoryLoader::LoadDirectoryFromServerAfterRefresh(
504    const DirectoryFetchInfo& directory_fetch_info,
505    const base::FilePath* directory_path,
506    FileError error) {
507  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
508
509  DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
510  OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
511
512  // Also notify the observers.
513  if (error == FILE_ERROR_OK && !directory_path->empty()) {
514    FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
515                      OnDirectoryChanged(*directory_path));
516  }
517}
518
519}  // namespace internal
520}  // namespace drive
521