Home | History | Annotate | Download | only in drive
      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:
     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:
     43   explicit FullFeedFetcher(JobScheduler* scheduler)
     44       : scheduler_(scheduler),
     45         weak_ptr_factory_(this) {
     46   }
     47 
     48   virtual ~FullFeedFetcher() {
     49   }
     50 
     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:
     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:
    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 
    114   virtual ~DeltaFeedFetcher() {
    115   }
    116 
    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:
    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 
    167 LoaderController::LoaderController()
    168     : lock_count_(0),
    169       weak_ptr_factory_(this) {
    170   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    171 }
    172 
    173 LoaderController::~LoaderController() {
    174   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    175 }
    176 
    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 
    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 
    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 
    210 AboutResourceLoader::AboutResourceLoader(JobScheduler* scheduler)
    211     : scheduler_(scheduler),
    212       weak_ptr_factory_(this) {
    213 }
    214 
    215 AboutResourceLoader::~AboutResourceLoader() {}
    216 
    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 
    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 
    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 
    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 
    287 ChangeListLoader::~ChangeListLoader() {
    288 }
    289 
    290 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 
    296 void ChangeListLoader::AddObserver(ChangeListLoaderObserver* observer) {
    297   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    298   observers_.AddObserver(observer);
    299 }
    300 
    301 void ChangeListLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
    302   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    303   observers_.RemoveObserver(observer);
    304 }
    305 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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