Move sync parts of ExtensionService into new class ExtensionSyncService.

This makes ExtensionService easier to reason about / grapple with.

TBR=zea
BUG=298537

Review URL: https://codereview.chromium.org/26896006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@231781 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc
new file mode 100644
index 0000000..5adab024
--- /dev/null
+++ b/chrome/browser/extensions/extension_sync_service.cc
@@ -0,0 +1,443 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/extension_sync_service.h"
+
+#include <iterator>
+
+#include "base/basictypes.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/extensions/app_sync_data.h"
+#include "chrome/browser/extensions/extension_error_ui.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_sorting.h"
+#include "chrome/browser/extensions/extension_sync_data.h"
+#include "chrome/browser/extensions/extension_sync_service_factory.h"
+#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/glue/sync_start_util.h"
+#include "chrome/browser/sync/sync_prefs.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/feature_switch.h"
+#include "chrome/common/extensions/sync_helper.h"
+#include "content/public/browser/browser_thread.h"
+#include "extensions/common/manifest_constants.h"
+#include "sync/api/sync_change.h"
+#include "sync/api/sync_error_factory.h"
+
+using extensions::Extension;
+using extensions::ExtensionPrefs;
+using extensions::FeatureSwitch;
+
+ExtensionSyncService::ExtensionSyncService(Profile* profile,
+                                           ExtensionPrefs* extension_prefs,
+                                           ExtensionService* extension_service)
+    : profile_(profile),
+      extension_prefs_(extension_prefs),
+      extension_service_(extension_service),
+      app_sync_bundle_(this),
+      extension_sync_bundle_(this),
+      pending_app_enables_(
+          make_scoped_ptr(new browser_sync::SyncPrefs(
+              extension_prefs_->pref_service())),
+          &app_sync_bundle_,
+          syncer::APPS),
+      pending_extension_enables_(
+          make_scoped_ptr(new browser_sync::SyncPrefs(
+              extension_prefs_->pref_service())),
+          &extension_sync_bundle_,
+          syncer::EXTENSIONS) {
+  SetSyncStartFlare(sync_start_util::GetFlareForSyncableService(
+      profile_->GetPath()));
+
+  extension_service_->set_extension_sync_service(this);
+  extension_prefs_->extension_sorting()->SetExtensionSyncService(this);
+}
+
+ExtensionSyncService::~ExtensionSyncService() {}
+
+// static
+ExtensionSyncService* ExtensionSyncService::Get(Profile* profile) {
+  return ExtensionSyncServiceFactory::GetForProfile(profile);
+}
+
+syncer::SyncChange ExtensionSyncService::PrepareToSyncUninstallExtension(
+    const extensions::Extension* extension, bool extensions_ready) {
+  // Extract the data we need for sync now, but don't actually sync until we've
+  // completed the uninstallation.
+  // TODO(tim): If we get here and IsSyncing is false, this will cause
+  // "back from the dead" style bugs, because sync will add-back the extension
+  // that was uninstalled here when MergeDataAndStartSyncing is called.
+  // See crbug.com/256795.
+  if (extensions::sync_helper::IsSyncableApp(extension)) {
+    if (app_sync_bundle_.IsSyncing())
+      return app_sync_bundle_.CreateSyncChangeToDelete(extension);
+    else if (extensions_ready && !flare_.is_null())
+      flare_.Run(syncer::APPS);  // Tell sync to start ASAP.
+  } else if (extensions::sync_helper::IsSyncableExtension(extension)) {
+    if (extension_sync_bundle_.IsSyncing())
+      return extension_sync_bundle_.CreateSyncChangeToDelete(extension);
+    else if (extensions_ready && !flare_.is_null())
+      flare_.Run(syncer::EXTENSIONS);  // Tell sync to start ASAP.
+  }
+
+  return syncer::SyncChange();
+}
+
+void ExtensionSyncService::ProcessSyncUninstallExtension(
+    const std::string& extension_id,
+    const syncer::SyncChange& sync_change) {
+  if (app_sync_bundle_.HasExtensionId(extension_id) &&
+      sync_change.sync_data().GetDataType() == syncer::APPS) {
+    app_sync_bundle_.ProcessDeletion(extension_id, sync_change);
+  } else if (extension_sync_bundle_.HasExtensionId(extension_id) &&
+             sync_change.sync_data().GetDataType() == syncer::EXTENSIONS) {
+    extension_sync_bundle_.ProcessDeletion(extension_id, sync_change);
+  }
+}
+
+void ExtensionSyncService::SyncEnableExtension(
+    const extensions::Extension& extension) {
+
+  // Syncing may not have started yet, so handle pending enables.
+  if (extensions::sync_helper::IsSyncableApp(&extension))
+    pending_app_enables_.OnExtensionEnabled(extension.id());
+
+  if (extensions::sync_helper::IsSyncableExtension(&extension))
+    pending_extension_enables_.OnExtensionEnabled(extension.id());
+
+  SyncExtensionChangeIfNeeded(extension);
+}
+
+void ExtensionSyncService::SyncDisableExtension(
+    const extensions::Extension& extension) {
+
+  // Syncing may not have started yet, so handle pending enables.
+  if (extensions::sync_helper::IsSyncableApp(&extension))
+    pending_app_enables_.OnExtensionDisabled(extension.id());
+
+  if (extensions::sync_helper::IsSyncableExtension(&extension))
+    pending_extension_enables_.OnExtensionDisabled(extension.id());
+
+  SyncExtensionChangeIfNeeded(extension);
+}
+
+syncer::SyncMergeResult ExtensionSyncService::MergeDataAndStartSyncing(
+    syncer::ModelType type,
+    const syncer::SyncDataList& initial_sync_data,
+    scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
+    scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
+  CHECK(sync_processor.get());
+  CHECK(sync_error_factory.get());
+
+  switch (type) {
+    case syncer::EXTENSIONS:
+      extension_sync_bundle_.SetupSync(sync_processor.release(),
+                                       sync_error_factory.release(),
+                                       initial_sync_data);
+      pending_extension_enables_.OnSyncStarted(extension_service_);
+      break;
+
+    case syncer::APPS:
+      app_sync_bundle_.SetupSync(sync_processor.release(),
+                                 sync_error_factory.release(),
+                                 initial_sync_data);
+      pending_app_enables_.OnSyncStarted(extension_service_);
+      break;
+
+    default:
+      LOG(FATAL) << "Got " << type << " ModelType";
+  }
+
+  // Process local extensions.
+  // TODO(yoz): Determine whether pending extensions should be considered too.
+  //            See crbug.com/104399.
+  syncer::SyncDataList sync_data_list = GetAllSyncData(type);
+  syncer::SyncChangeList sync_change_list;
+  for (syncer::SyncDataList::const_iterator i = sync_data_list.begin();
+       i != sync_data_list.end();
+       ++i) {
+    switch (type) {
+        case syncer::EXTENSIONS:
+          sync_change_list.push_back(
+              extension_sync_bundle_.CreateSyncChange(*i));
+          break;
+        case syncer::APPS:
+          sync_change_list.push_back(app_sync_bundle_.CreateSyncChange(*i));
+          break;
+      default:
+        LOG(FATAL) << "Got " << type << " ModelType";
+    }
+  }
+
+
+  if (type == syncer::EXTENSIONS) {
+    extension_sync_bundle_.ProcessSyncChangeList(sync_change_list);
+  } else if (type == syncer::APPS) {
+    app_sync_bundle_.ProcessSyncChangeList(sync_change_list);
+  }
+
+  return syncer::SyncMergeResult(type);
+}
+
+void ExtensionSyncService::StopSyncing(syncer::ModelType type) {
+  if (type == syncer::APPS) {
+    app_sync_bundle_.Reset();
+  } else if (type == syncer::EXTENSIONS) {
+    extension_sync_bundle_.Reset();
+  }
+}
+
+syncer::SyncDataList ExtensionSyncService::GetAllSyncData(
+    syncer::ModelType type) const {
+  if (type == syncer::EXTENSIONS)
+    return extension_sync_bundle_.GetAllSyncData();
+  if (type == syncer::APPS)
+    return app_sync_bundle_.GetAllSyncData();
+
+  // We should only get sync data for extensions and apps.
+  NOTREACHED();
+
+  return syncer::SyncDataList();
+}
+
+syncer::SyncError ExtensionSyncService::ProcessSyncChanges(
+    const tracked_objects::Location& from_here,
+    const syncer::SyncChangeList& change_list) {
+  for (syncer::SyncChangeList::const_iterator i = change_list.begin();
+      i != change_list.end();
+      ++i) {
+    syncer::ModelType type = i->sync_data().GetDataType();
+    if (type == syncer::EXTENSIONS) {
+      extension_sync_bundle_.ProcessSyncChange(
+          extensions::ExtensionSyncData(*i));
+    } else if (type == syncer::APPS) {
+      app_sync_bundle_.ProcessSyncChange(extensions::AppSyncData(*i));
+    }
+  }
+
+  extension_prefs_->extension_sorting()->FixNTPOrdinalCollisions();
+
+  return syncer::SyncError();
+}
+
+extensions::ExtensionSyncData ExtensionSyncService::GetExtensionSyncData(
+    const Extension& extension) const {
+  return extensions::ExtensionSyncData(
+      extension,
+      extension_service_->IsExtensionEnabled(extension.id()),
+      extension_util::IsIncognitoEnabled(extension.id(), extension_service_));
+}
+
+extensions::AppSyncData ExtensionSyncService::GetAppSyncData(
+    const Extension& extension) const {
+  return extensions::AppSyncData(
+      extension,
+      extension_service_->IsExtensionEnabled(extension.id()),
+      extension_util::IsIncognitoEnabled(extension.id(), extension_service_),
+      extension_prefs_->extension_sorting()->GetAppLaunchOrdinal(
+          extension.id()),
+      extension_prefs_->extension_sorting()->GetPageOrdinal(extension.id()));
+}
+
+std::vector<extensions::ExtensionSyncData>
+  ExtensionSyncService::GetExtensionSyncDataList() const {
+  std::vector<extensions::ExtensionSyncData> extension_sync_list;
+  extension_sync_bundle_.GetExtensionSyncDataListHelper(
+      extension_service_->extensions(), &extension_sync_list);
+  extension_sync_bundle_.GetExtensionSyncDataListHelper(
+      extension_service_->disabled_extensions(), &extension_sync_list);
+  extension_sync_bundle_.GetExtensionSyncDataListHelper(
+      extension_service_->terminated_extensions(), &extension_sync_list);
+
+  std::vector<extensions::ExtensionSyncData> pending_extensions =
+      extension_sync_bundle_.GetPendingData();
+  extension_sync_list.insert(extension_sync_list.begin(),
+                             pending_extensions.begin(),
+                             pending_extensions.end());
+
+  return extension_sync_list;
+}
+
+std::vector<extensions::AppSyncData> ExtensionSyncService::GetAppSyncDataList()
+    const {
+  std::vector<extensions::AppSyncData> app_sync_list;
+  app_sync_bundle_.GetAppSyncDataListHelper(
+      extension_service_->extensions(), &app_sync_list);
+  app_sync_bundle_.GetAppSyncDataListHelper(
+      extension_service_->disabled_extensions(), &app_sync_list);
+  app_sync_bundle_.GetAppSyncDataListHelper(
+      extension_service_->terminated_extensions(), &app_sync_list);
+
+  std::vector<extensions::AppSyncData> pending_apps =
+      app_sync_bundle_.GetPendingData();
+  app_sync_list.insert(app_sync_list.begin(),
+                       pending_apps.begin(),
+                       pending_apps.end());
+
+  return app_sync_list;
+}
+
+bool ExtensionSyncService::ProcessExtensionSyncData(
+    const extensions::ExtensionSyncData& extension_sync_data) {
+  if (!ProcessExtensionSyncDataHelper(extension_sync_data,
+                                      syncer::EXTENSIONS)) {
+    extension_sync_bundle_.AddPendingExtension(extension_sync_data.id(),
+                                               extension_sync_data);
+    extension_service_->CheckForUpdatesSoon();
+    return false;
+  }
+
+  return true;
+}
+
+bool ExtensionSyncService::ProcessAppSyncData(
+    const extensions::AppSyncData& app_sync_data) {
+  const std::string& id = app_sync_data.id();
+
+  if (app_sync_data.app_launch_ordinal().IsValid() &&
+      app_sync_data.page_ordinal().IsValid()) {
+    extension_prefs_->extension_sorting()->SetAppLaunchOrdinal(
+        id,
+        app_sync_data.app_launch_ordinal());
+    extension_prefs_->extension_sorting()->SetPageOrdinal(
+        id,
+        app_sync_data.page_ordinal());
+  }
+
+  if (!ProcessExtensionSyncDataHelper(app_sync_data.extension_sync_data(),
+                                      syncer::APPS)) {
+    app_sync_bundle_.AddPendingApp(id, app_sync_data);
+    extension_service_->CheckForUpdatesSoon();
+    return false;
+  }
+
+  return true;
+}
+
+void ExtensionSyncService::SyncOrderingChange(const std::string& extension_id) {
+  const extensions::Extension* ext = extension_service_->GetInstalledExtension(
+      extension_id);
+
+  if (ext)
+    SyncExtensionChangeIfNeeded(*ext);
+}
+
+void ExtensionSyncService::SetSyncStartFlare(
+    const syncer::SyncableService::StartSyncFlare& flare) {
+  flare_ = flare;
+}
+
+bool ExtensionSyncService::IsCorrectSyncType(const Extension& extension,
+                                         syncer::ModelType type) const {
+  if (type == syncer::EXTENSIONS &&
+      extensions::sync_helper::IsSyncableExtension(&extension)) {
+    return true;
+  }
+
+  if (type == syncer::APPS &&
+      extensions::sync_helper::IsSyncableApp(&extension)) {
+    return true;
+  }
+
+  return false;
+}
+
+bool ExtensionSyncService::IsPendingEnable(
+    const std::string& extension_id) const {
+  return pending_app_enables_.Contains(extension_id) ||
+      pending_extension_enables_.Contains(extension_id);
+}
+
+bool ExtensionSyncService::ProcessExtensionSyncDataHelper(
+    const extensions::ExtensionSyncData& extension_sync_data,
+    syncer::ModelType type) {
+  const std::string& id = extension_sync_data.id();
+  const Extension* extension = extension_service_->GetInstalledExtension(id);
+
+  // TODO(bolms): we should really handle this better.  The particularly bad
+  // case is where an app becomes an extension or vice versa, and we end up with
+  // a zombie extension that won't go away.
+  if (extension && !IsCorrectSyncType(*extension, type))
+    return true;
+
+  // Handle uninstalls first.
+  if (extension_sync_data.uninstalled()) {
+    if (!extension_service_->UninstallExtensionHelper(extension_service_, id)) {
+      LOG(WARNING) << "Could not uninstall extension " << id
+                   << " for sync";
+    }
+    return true;
+  }
+
+  // Extension from sync was uninstalled by the user as external extensions.
+  // Honor user choice and skip installation/enabling.
+  if (extension_service_->IsExternalExtensionUninstalled(id)) {
+    LOG(WARNING) << "Extension with id " << id
+                 << " from sync was uninstalled as external extension";
+    return true;
+  }
+
+  // Set user settings.
+
+  // We need to cache some version information here because setting the
+  // incognito flag invalidates the |extension| pointer (it reloads the
+  // extension).
+  bool extension_installed = (extension != NULL);
+  int result = extension ?
+      extension->version()->CompareTo(extension_sync_data.version()) : 0;
+  extension_util::SetIsIncognitoEnabled(
+      id, extension_service_, extension_sync_data.incognito_enabled());
+  extension = NULL;  // No longer safe to use.
+
+  if (extension_installed) {
+    // If the extension is already installed, check if it's outdated.
+    if (result < 0) {
+      // Extension is outdated.
+      return false;
+    }
+  } else {
+    // TODO(akalin): Replace silent update with a list of enabled
+    // permissions.
+    const bool kInstallSilently = true;
+
+    CHECK(type == syncer::EXTENSIONS || type == syncer::APPS);
+    extensions::PendingExtensionInfo::ShouldAllowInstallPredicate filter =
+        (type == syncer::APPS) ? extensions::sync_helper::IsSyncableApp :
+                                 extensions::sync_helper::IsSyncableExtension;
+
+    if (!extension_service_->pending_extension_manager()->AddFromSync(
+            id,
+            extension_sync_data.update_url(),
+            filter,
+            kInstallSilently)) {
+      LOG(WARNING) << "Could not add pending extension for " << id;
+      // This means that the extension is already pending installation, with a
+      // non-INTERNAL location.  Add to pending_sync_data, even though it will
+      // never be removed (we'll never install a syncable version of the
+      // extension), so that GetAllSyncData() continues to send it.
+    }
+    // Track pending extensions so that we can return them in GetAllSyncData().
+    return false;
+  }
+
+  return true;
+}
+
+void ExtensionSyncService::SyncExtensionChangeIfNeeded(
+    const Extension& extension) {
+  if (extensions::sync_helper::IsSyncableApp(&extension)) {
+    if (app_sync_bundle_.IsSyncing())
+      app_sync_bundle_.SyncChangeIfNeeded(extension);
+    else if (extension_service_->is_ready() && !flare_.is_null())
+      flare_.Run(syncer::APPS);
+  } else if (extensions::sync_helper::IsSyncableExtension(&extension)) {
+    if (extension_sync_bundle_.IsSyncing())
+      extension_sync_bundle_.SyncChangeIfNeeded(extension);
+    else if (extension_service_->is_ready() && !flare_.is_null())
+      flare_.Run(syncer::EXTENSIONS);
+  }
+}