• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "base/time/time.h"
14 #include "chrome/browser/chromeos/drive/change_list_loader_observer.h"
15 #include "chrome/browser/chromeos/drive/change_list_processor.h"
16 #include "chrome/browser/chromeos/drive/file_system_util.h"
17 #include "chrome/browser/chromeos/drive/job_scheduler.h"
18 #include "chrome/browser/chromeos/drive/resource_metadata.h"
19 #include "chrome/browser/drive/event_logger.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "google_apis/drive/drive_api_parser.h"
22 #include "url/gurl.h"
23 
24 using content::BrowserThread;
25 
26 namespace drive {
27 namespace internal {
28 
29 typedef base::Callback<void(FileError, ScopedVector<ChangeList>)>
30     FeedFetcherCallback;
31 
32 class ChangeListLoader::FeedFetcher {
33  public:
~FeedFetcher()34   virtual ~FeedFetcher() {}
35   virtual void Run(const FeedFetcherCallback& callback) = 0;
36 };
37 
38 namespace {
39 
40 // Fetches all the (currently available) resource entries from the server.
41 class FullFeedFetcher : public ChangeListLoader::FeedFetcher {
42  public:
FullFeedFetcher(JobScheduler * scheduler)43   explicit FullFeedFetcher(JobScheduler* scheduler)
44       : scheduler_(scheduler),
45         weak_ptr_factory_(this) {
46   }
47 
~FullFeedFetcher()48   virtual ~FullFeedFetcher() {
49   }
50 
Run(const FeedFetcherCallback & callback)51   virtual void Run(const FeedFetcherCallback& callback) OVERRIDE {
52     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
53     DCHECK(!callback.is_null());
54 
55     // Remember the time stamp for usage stats.
56     start_time_ = base::TimeTicks::Now();
57 
58     // This is full resource list fetch.
59     scheduler_->GetAllFileList(
60         base::Bind(&FullFeedFetcher::OnFileListFetched,
61                    weak_ptr_factory_.GetWeakPtr(), callback));
62   }
63 
64  private:
OnFileListFetched(const FeedFetcherCallback & callback,google_apis::GDataErrorCode status,scoped_ptr<google_apis::FileList> file_list)65   void OnFileListFetched(const FeedFetcherCallback& callback,
66                          google_apis::GDataErrorCode status,
67                          scoped_ptr<google_apis::FileList> file_list) {
68     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
69     DCHECK(!callback.is_null());
70 
71     FileError error = GDataToFileError(status);
72     if (error != FILE_ERROR_OK) {
73       callback.Run(error, ScopedVector<ChangeList>());
74       return;
75     }
76 
77     DCHECK(file_list);
78     change_lists_.push_back(new ChangeList(*file_list));
79 
80     if (!file_list->next_link().is_empty()) {
81       // There is the remaining result so fetch it.
82       scheduler_->GetRemainingFileList(
83           file_list->next_link(),
84           base::Bind(&FullFeedFetcher::OnFileListFetched,
85                      weak_ptr_factory_.GetWeakPtr(), callback));
86       return;
87     }
88 
89     UMA_HISTOGRAM_LONG_TIMES("Drive.FullFeedLoadTime",
90                              base::TimeTicks::Now() - start_time_);
91 
92     // Note: The fetcher is managed by ChangeListLoader, and the instance
93     // will be deleted in the callback. Do not touch the fields after this
94     // invocation.
95     callback.Run(FILE_ERROR_OK, change_lists_.Pass());
96   }
97 
98   JobScheduler* scheduler_;
99   ScopedVector<ChangeList> change_lists_;
100   base::TimeTicks start_time_;
101   base::WeakPtrFactory<FullFeedFetcher> weak_ptr_factory_;
102   DISALLOW_COPY_AND_ASSIGN(FullFeedFetcher);
103 };
104 
105 // Fetches the delta changes since |start_change_id|.
106 class DeltaFeedFetcher : public ChangeListLoader::FeedFetcher {
107  public:
DeltaFeedFetcher(JobScheduler * scheduler,int64 start_change_id)108   DeltaFeedFetcher(JobScheduler* scheduler, int64 start_change_id)
109       : scheduler_(scheduler),
110         start_change_id_(start_change_id),
111         weak_ptr_factory_(this) {
112   }
113 
~DeltaFeedFetcher()114   virtual ~DeltaFeedFetcher() {
115   }
116 
Run(const FeedFetcherCallback & callback)117   virtual void Run(const FeedFetcherCallback& callback) OVERRIDE {
118     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
119     DCHECK(!callback.is_null());
120 
121     scheduler_->GetChangeList(
122         start_change_id_,
123         base::Bind(&DeltaFeedFetcher::OnChangeListFetched,
124                    weak_ptr_factory_.GetWeakPtr(), callback));
125   }
126 
127  private:
OnChangeListFetched(const FeedFetcherCallback & callback,google_apis::GDataErrorCode status,scoped_ptr<google_apis::ChangeList> change_list)128   void OnChangeListFetched(const FeedFetcherCallback& callback,
129                            google_apis::GDataErrorCode status,
130                            scoped_ptr<google_apis::ChangeList> change_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     DCHECK(change_list);
141     change_lists_.push_back(new ChangeList(*change_list));
142 
143     if (!change_list->next_link().is_empty()) {
144       // There is the remaining result so fetch it.
145       scheduler_->GetRemainingChangeList(
146           change_list->next_link(),
147           base::Bind(&DeltaFeedFetcher::OnChangeListFetched,
148                      weak_ptr_factory_.GetWeakPtr(), callback));
149       return;
150     }
151 
152     // Note: The fetcher is managed by ChangeListLoader, and the instance
153     // will be deleted in the callback. Do not touch the fields after this
154     // invocation.
155     callback.Run(FILE_ERROR_OK, change_lists_.Pass());
156   }
157 
158   JobScheduler* scheduler_;
159   int64 start_change_id_;
160   ScopedVector<ChangeList> change_lists_;
161   base::WeakPtrFactory<DeltaFeedFetcher> weak_ptr_factory_;
162   DISALLOW_COPY_AND_ASSIGN(DeltaFeedFetcher);
163 };
164 
165 }  // namespace
166 
LoaderController()167 LoaderController::LoaderController()
168     : lock_count_(0),
169       weak_ptr_factory_(this) {
170   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
171 }
172 
~LoaderController()173 LoaderController::~LoaderController() {
174   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
175 }
176 
GetLock()177 scoped_ptr<base::ScopedClosureRunner> LoaderController::GetLock() {
178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179 
180   ++lock_count_;
181   return make_scoped_ptr(new base::ScopedClosureRunner(
182       base::Bind(&LoaderController::Unlock,
183                  weak_ptr_factory_.GetWeakPtr())));
184 }
185 
ScheduleRun(const base::Closure & task)186 void LoaderController::ScheduleRun(const base::Closure& task) {
187   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
188   DCHECK(!task.is_null());
189 
190   if (lock_count_ > 0) {
191     pending_tasks_.push_back(task);
192   } else {
193     task.Run();
194   }
195 }
196 
Unlock()197 void LoaderController::Unlock() {
198   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
199   DCHECK_LT(0, lock_count_);
200 
201   if (--lock_count_ > 0)
202     return;
203 
204   std::vector<base::Closure> tasks;
205   tasks.swap(pending_tasks_);
206   for (size_t i = 0; i < tasks.size(); ++i)
207     tasks[i].Run();
208 }
209 
AboutResourceLoader(JobScheduler * scheduler)210 AboutResourceLoader::AboutResourceLoader(JobScheduler* scheduler)
211     : scheduler_(scheduler),
212       weak_ptr_factory_(this) {
213 }
214 
~AboutResourceLoader()215 AboutResourceLoader::~AboutResourceLoader() {}
216 
GetAboutResource(const google_apis::AboutResourceCallback & callback)217 void AboutResourceLoader::GetAboutResource(
218     const google_apis::AboutResourceCallback& callback) {
219   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
220   DCHECK(!callback.is_null());
221 
222   if (cached_about_resource_) {
223     base::MessageLoopProxy::current()->PostTask(
224         FROM_HERE,
225         base::Bind(
226             callback,
227             google_apis::HTTP_NO_CONTENT,
228             base::Passed(scoped_ptr<google_apis::AboutResource>(
229                 new google_apis::AboutResource(*cached_about_resource_)))));
230   } else {
231     UpdateAboutResource(callback);
232   }
233 }
234 
UpdateAboutResource(const google_apis::AboutResourceCallback & callback)235 void AboutResourceLoader::UpdateAboutResource(
236     const google_apis::AboutResourceCallback& callback) {
237   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
238   DCHECK(!callback.is_null());
239 
240   scheduler_->GetAboutResource(
241       base::Bind(&AboutResourceLoader::UpdateAboutResourceAfterGetAbout,
242                  weak_ptr_factory_.GetWeakPtr(),
243                  callback));
244 }
245 
UpdateAboutResourceAfterGetAbout(const google_apis::AboutResourceCallback & callback,google_apis::GDataErrorCode status,scoped_ptr<google_apis::AboutResource> about_resource)246 void AboutResourceLoader::UpdateAboutResourceAfterGetAbout(
247     const google_apis::AboutResourceCallback& callback,
248     google_apis::GDataErrorCode status,
249     scoped_ptr<google_apis::AboutResource> about_resource) {
250   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
251   DCHECK(!callback.is_null());
252   FileError error = GDataToFileError(status);
253 
254   if (error == FILE_ERROR_OK) {
255     if (cached_about_resource_ &&
256         cached_about_resource_->largest_change_id() >
257         about_resource->largest_change_id()) {
258       LOG(WARNING) << "Local cached about resource is fresher than server, "
259                    << "local = " << cached_about_resource_->largest_change_id()
260                    << ", server = " << about_resource->largest_change_id();
261     }
262 
263     cached_about_resource_.reset(
264         new google_apis::AboutResource(*about_resource));
265   }
266 
267   callback.Run(status, about_resource.Pass());
268 }
269 
ChangeListLoader(EventLogger * logger,base::SequencedTaskRunner * blocking_task_runner,ResourceMetadata * resource_metadata,JobScheduler * scheduler,AboutResourceLoader * about_resource_loader,LoaderController * loader_controller)270 ChangeListLoader::ChangeListLoader(
271     EventLogger* logger,
272     base::SequencedTaskRunner* blocking_task_runner,
273     ResourceMetadata* resource_metadata,
274     JobScheduler* scheduler,
275     AboutResourceLoader* about_resource_loader,
276     LoaderController* loader_controller)
277     : logger_(logger),
278       blocking_task_runner_(blocking_task_runner),
279       resource_metadata_(resource_metadata),
280       scheduler_(scheduler),
281       about_resource_loader_(about_resource_loader),
282       loader_controller_(loader_controller),
283       loaded_(false),
284       weak_ptr_factory_(this) {
285 }
286 
~ChangeListLoader()287 ChangeListLoader::~ChangeListLoader() {
288 }
289 
IsRefreshing() const290 bool ChangeListLoader::IsRefreshing() const {
291   // Callback for change list loading is stored in pending_load_callback_.
292   // It is non-empty if and only if there is an in-flight loading operation.
293   return !pending_load_callback_.empty();
294 }
295 
AddObserver(ChangeListLoaderObserver * observer)296 void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) {
297   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298   observers_.AddObserver(observer);
299 }
300 
RemoveObserver(ChangeListLoaderObserver * observer)301 void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
302   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
303   observers_.RemoveObserver(observer);
304 }
305 
CheckForUpdates(const FileOperationCallback & callback)306 void ChangeListLoader::CheckForUpdates(const FileOperationCallback& callback) {
307   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308   DCHECK(!callback.is_null());
309 
310   if (IsRefreshing()) {
311     // There is in-flight loading. So keep the callback here, and check for
312     // updates when the in-flight loading is completed.
313     pending_update_check_callback_ = callback;
314     return;
315   }
316 
317   if (loaded_) {
318     // We only start to check for updates iff the load is done.
319     // I.e., we ignore checking updates if not loaded to avoid starting the
320     // load without user's explicit interaction (such as opening Drive).
321     logger_->Log(logging::LOG_INFO, "Checking for updates");
322     Load(callback);
323   }
324 }
325 
LoadIfNeeded(const FileOperationCallback & callback)326 void ChangeListLoader::LoadIfNeeded(const FileOperationCallback& callback) {
327   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
328   DCHECK(!callback.is_null());
329 
330   // If the metadata is not yet loaded, start loading.
331   if (!loaded_)
332     Load(callback);
333 }
334 
Load(const FileOperationCallback & callback)335 void ChangeListLoader::Load(const FileOperationCallback& callback) {
336   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
337   DCHECK(!callback.is_null());
338 
339   // Check if this is the first time this ChangeListLoader do loading.
340   // Note: IsRefreshing() depends on pending_load_callback_ so check in advance.
341   const bool is_initial_load = (!loaded_ && !IsRefreshing());
342 
343   // Register the callback function to be called when it is loaded.
344   pending_load_callback_.push_back(callback);
345 
346   // If loading task is already running, do nothing.
347   if (pending_load_callback_.size() > 1)
348     return;
349 
350   // Check the current status of local metadata, and start loading if needed.
351   int64* local_changestamp = new int64(0);
352   base::PostTaskAndReplyWithResult(
353       blocking_task_runner_,
354       FROM_HERE,
355       base::Bind(&ResourceMetadata::GetLargestChangestamp,
356                  base::Unretained(resource_metadata_),
357                  local_changestamp),
358       base::Bind(&ChangeListLoader::LoadAfterGetLargestChangestamp,
359                  weak_ptr_factory_.GetWeakPtr(),
360                  is_initial_load,
361                  base::Owned(local_changestamp)));
362 }
363 
LoadAfterGetLargestChangestamp(bool is_initial_load,const int64 * local_changestamp,FileError error)364 void ChangeListLoader::LoadAfterGetLargestChangestamp(
365     bool is_initial_load,
366     const int64* local_changestamp,
367     FileError error) {
368   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
369 
370   if (error != FILE_ERROR_OK) {
371     OnChangeListLoadComplete(error);
372     return;
373   }
374 
375   if (is_initial_load && *local_changestamp > 0) {
376     // The local data is usable. Flush callbacks to tell loading was successful.
377     OnChangeListLoadComplete(FILE_ERROR_OK);
378 
379     // Continues to load from server in background.
380     // Put dummy callbacks to indicate that fetching is still continuing.
381     pending_load_callback_.push_back(
382         base::Bind(&util::EmptyFileOperationCallback));
383   }
384 
385   about_resource_loader_->UpdateAboutResource(
386       base::Bind(&ChangeListLoader::LoadAfterGetAboutResource,
387                  weak_ptr_factory_.GetWeakPtr(),
388                  *local_changestamp));
389 }
390 
LoadAfterGetAboutResource(int64 local_changestamp,google_apis::GDataErrorCode status,scoped_ptr<google_apis::AboutResource> about_resource)391 void ChangeListLoader::LoadAfterGetAboutResource(
392     int64 local_changestamp,
393     google_apis::GDataErrorCode status,
394     scoped_ptr<google_apis::AboutResource> about_resource) {
395   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
396 
397   FileError error = GDataToFileError(status);
398   if (error != FILE_ERROR_OK) {
399     OnChangeListLoadComplete(error);
400     return;
401   }
402 
403   DCHECK(about_resource);
404 
405   int64 remote_changestamp = about_resource->largest_change_id();
406   int64 start_changestamp = local_changestamp > 0 ? local_changestamp + 1 : 0;
407   if (local_changestamp >= remote_changestamp) {
408     if (local_changestamp > remote_changestamp) {
409       LOG(WARNING) << "Local resource metadata is fresher than server, "
410                    << "local = " << local_changestamp
411                    << ", server = " << remote_changestamp;
412     }
413 
414     // No changes detected, tell the client that the loading was successful.
415     OnChangeListLoadComplete(FILE_ERROR_OK);
416   } else {
417     // Start loading the change list.
418     LoadChangeListFromServer(start_changestamp);
419   }
420 }
421 
OnChangeListLoadComplete(FileError error)422 void ChangeListLoader::OnChangeListLoadComplete(FileError error) {
423   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
424 
425   if (!loaded_ && error == FILE_ERROR_OK) {
426     loaded_ = true;
427     FOR_EACH_OBSERVER(ChangeListLoaderObserver,
428                       observers_,
429                       OnInitialLoadComplete());
430   }
431 
432   for (size_t i = 0; i < pending_load_callback_.size(); ++i) {
433     base::MessageLoopProxy::current()->PostTask(
434         FROM_HERE,
435         base::Bind(pending_load_callback_[i], error));
436   }
437   pending_load_callback_.clear();
438 
439   // If there is pending update check, try to load the change from the server
440   // again, because there may exist an update during the completed loading.
441   if (!pending_update_check_callback_.is_null()) {
442     Load(base::ResetAndReturn(&pending_update_check_callback_));
443   }
444 }
445 
LoadChangeListFromServer(int64 start_changestamp)446 void ChangeListLoader::LoadChangeListFromServer(int64 start_changestamp) {
447   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
448   DCHECK(!change_feed_fetcher_);
449   DCHECK(about_resource_loader_->cached_about_resource());
450 
451   bool is_delta_update = start_changestamp != 0;
452 
453   // Set up feed fetcher.
454   if (is_delta_update) {
455     change_feed_fetcher_.reset(
456         new DeltaFeedFetcher(scheduler_, start_changestamp));
457   } else {
458     change_feed_fetcher_.reset(new FullFeedFetcher(scheduler_));
459   }
460 
461   // Make a copy of cached_about_resource_ to remember at which changestamp we
462   // are fetching change list.
463   change_feed_fetcher_->Run(
464       base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList,
465                  weak_ptr_factory_.GetWeakPtr(),
466                  base::Passed(make_scoped_ptr(new google_apis::AboutResource(
467                      *about_resource_loader_->cached_about_resource()))),
468                  is_delta_update));
469 }
470 
LoadChangeListFromServerAfterLoadChangeList(scoped_ptr<google_apis::AboutResource> about_resource,bool is_delta_update,FileError error,ScopedVector<ChangeList> change_lists)471 void ChangeListLoader::LoadChangeListFromServerAfterLoadChangeList(
472     scoped_ptr<google_apis::AboutResource> about_resource,
473     bool is_delta_update,
474     FileError error,
475     ScopedVector<ChangeList> change_lists) {
476   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
477   DCHECK(about_resource);
478 
479   // Delete the fetcher first.
480   change_feed_fetcher_.reset();
481 
482   if (error != FILE_ERROR_OK) {
483     OnChangeListLoadComplete(error);
484     return;
485   }
486 
487   ChangeListProcessor* change_list_processor =
488       new ChangeListProcessor(resource_metadata_);
489   // Don't send directory content change notification while performing
490   // the initial content retrieval.
491   const bool should_notify_changed_directories = is_delta_update;
492 
493   logger_->Log(logging::LOG_INFO,
494                "Apply change lists (is delta: %d)",
495                is_delta_update);
496   loader_controller_->ScheduleRun(base::Bind(
497       base::IgnoreResult(
498           &base::PostTaskAndReplyWithResult<FileError, FileError>),
499       blocking_task_runner_,
500       FROM_HERE,
501       base::Bind(&ChangeListProcessor::Apply,
502                  base::Unretained(change_list_processor),
503                  base::Passed(&about_resource),
504                  base::Passed(&change_lists),
505                  is_delta_update),
506       base::Bind(&ChangeListLoader::LoadChangeListFromServerAfterUpdate,
507                  weak_ptr_factory_.GetWeakPtr(),
508                  base::Owned(change_list_processor),
509                  should_notify_changed_directories,
510                  base::Time::Now())));
511 }
512 
LoadChangeListFromServerAfterUpdate(ChangeListProcessor * change_list_processor,bool should_notify_changed_directories,const base::Time & start_time,FileError error)513 void ChangeListLoader::LoadChangeListFromServerAfterUpdate(
514     ChangeListProcessor* change_list_processor,
515     bool should_notify_changed_directories,
516     const base::Time& start_time,
517     FileError error) {
518   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
519 
520   const base::TimeDelta elapsed = base::Time::Now() - start_time;
521   logger_->Log(logging::LOG_INFO,
522                "Change lists applied (elapsed time: %sms)",
523                base::Int64ToString(elapsed.InMilliseconds()).c_str());
524 
525   if (should_notify_changed_directories) {
526     for (std::set<base::FilePath>::iterator dir_iter =
527             change_list_processor->changed_dirs().begin();
528         dir_iter != change_list_processor->changed_dirs().end();
529         ++dir_iter) {
530       FOR_EACH_OBSERVER(ChangeListLoaderObserver, observers_,
531                         OnDirectoryChanged(*dir_iter));
532     }
533   }
534 
535   OnChangeListLoadComplete(error);
536 
537   FOR_EACH_OBSERVER(ChangeListLoaderObserver,
538                     observers_,
539                     OnLoadFromServerComplete());
540 }
541 
542 }  // namespace internal
543 }  // namespace drive
544