#include "chrome/browser/extensions/app_data_migrator.h"

#include "base/files/file_util.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/indexed_db_context.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
#include "storage/common/fileapi/file_system_types.h"

using base::WeakPtr;
using content::BrowserContext;
using content::BrowserThread;
using content::IndexedDBContext;
using content::StoragePartition;
using storage::FileSystemContext;
using storage::SandboxFileSystemBackendDelegate;

namespace {

void MigrateOnFileSystemThread(FileSystemContext* old_fs_context,
                               FileSystemContext* fs_context,
                               const extensions::Extension* extension) {
  DCHECK(
      old_fs_context->default_file_task_runner()->RunsTasksOnCurrentThread());

  SandboxFileSystemBackendDelegate* old_sandbox_delegate =
      old_fs_context->sandbox_delegate();
  SandboxFileSystemBackendDelegate* sandbox_delegate =
      fs_context->sandbox_delegate();

  GURL extension_url =
      extensions::Extension::GetBaseURLFromExtensionId(extension->id());

  scoped_ptr<storage::SandboxFileSystemBackendDelegate::OriginEnumerator>
      enumerator(old_sandbox_delegate->CreateOriginEnumerator());

  GURL origin;
  do {
    origin = enumerator->Next();
  } while (origin != extension_url && !origin.is_empty());

  if (!origin.is_empty()) {

    if (enumerator->HasFileSystemType(storage::kFileSystemTypeTemporary)) {
      old_sandbox_delegate->CopyFileSystem(
          extension_url, storage::kFileSystemTypeTemporary, sandbox_delegate);
    }

    if (enumerator->HasFileSystemType(storage::kFileSystemTypePersistent)) {
      old_sandbox_delegate->CopyFileSystem(
          extension_url, storage::kFileSystemTypePersistent, sandbox_delegate);
    }
  }
}

void MigrateOnIndexedDBThread(IndexedDBContext* old_indexed_db_context,
                              IndexedDBContext* indexed_db_context,
                              const extensions::Extension* extension) {
  DCHECK(old_indexed_db_context->TaskRunner()->RunsTasksOnCurrentThread());

  GURL extension_url =
      extensions::Extension::GetBaseURLFromExtensionId(extension->id());

  old_indexed_db_context->CopyOriginData(extension_url, indexed_db_context);
}

void MigrateFileSystem(WeakPtr<extensions::AppDataMigrator> migrator,
                       StoragePartition* old_partition,
                       StoragePartition* current_partition,
                       const extensions::Extension* extension,
                       const base::Closure& reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (!migrator.get()) {
    return;
  }

  FileSystemContext* old_fs_context = old_partition->GetFileSystemContext();
  FileSystemContext* fs_context = current_partition->GetFileSystemContext();

  old_fs_context->default_file_task_runner()->PostTaskAndReply(
      FROM_HERE,
      base::Bind(&MigrateOnFileSystemThread, make_scoped_refptr(old_fs_context),
                 make_scoped_refptr(fs_context), make_scoped_refptr(extension)),
      reply);
}

void MigrateLegacyPartition(WeakPtr<extensions::AppDataMigrator> migrator,
                            StoragePartition* old_partition,
                            StoragePartition* current_partition,
                            const extensions::Extension* extension,
                            const base::Closure& reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  IndexedDBContext* indexed_db_context =
      current_partition->GetIndexedDBContext();
  IndexedDBContext* old_indexed_db_context =
      old_partition->GetIndexedDBContext();

  base::Closure migrate_fs =
      base::Bind(&MigrateFileSystem, migrator, old_partition, current_partition,
                 make_scoped_refptr(extension), reply);

  old_indexed_db_context->TaskRunner()->PostTaskAndReply(
      FROM_HERE, base::Bind(&MigrateOnIndexedDBThread,
                            make_scoped_refptr(old_indexed_db_context),
                            make_scoped_refptr(indexed_db_context),
                            make_scoped_refptr(extension)),
      migrate_fs);
}

}  

namespace extensions {

AppDataMigrator::AppDataMigrator(Profile* profile, ExtensionRegistry* registry)
    : profile_(profile), registry_(registry), weak_factory_(this) {
}

AppDataMigrator::~AppDataMigrator() {
}

bool AppDataMigrator::NeedsMigration(const Extension* old,
                                     const Extension* extension) {
  return old && old->is_legacy_packaged_app() && extension->is_platform_app();
}

void AppDataMigrator::DoMigrationAndReply(const Extension* old,
                                          const Extension* extension,
                                          const base::Closure& reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(NeedsMigration(old, extension));

  content::StoragePartition* old_partition =
      BrowserContext::GetStoragePartitionForSite(
          profile_, Extension::GetBaseURLFromExtensionId(extension->id()));

  bool old_was_disabled = registry_->AddEnabled(extension);

  StoragePartition* new_partition = BrowserContext::GetStoragePartitionForSite(
      profile_, Extension::GetBaseURLFromExtensionId(extension->id()));

  if (old_was_disabled)
    registry_->RemoveEnabled(extension->id());
  else
    registry_->AddEnabled(old);

  MigrateLegacyPartition(weak_factory_.GetWeakPtr(), old_partition,
                         new_partition, extension, reply);
}

}
// Copyright 2015 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/app_data_migrator.h"

#include "base/files/file_util.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/indexed_db_context.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
#include "storage/common/fileapi/file_system_types.h"

using base::WeakPtr;
using content::BrowserContext;
using content::BrowserThread;
using content::IndexedDBContext;
using content::StoragePartition;
using storage::FileSystemContext;
using storage::SandboxFileSystemBackendDelegate;

namespace {

void MigrateOnFileSystemThread(FileSystemContext* old_fs_context,
                               FileSystemContext* fs_context,
                               const extensions::Extension* extension) {
  DCHECK(
      old_fs_context->default_file_task_runner()->RunsTasksOnCurrentThread());

  SandboxFileSystemBackendDelegate* old_sandbox_delegate =
      old_fs_context->sandbox_delegate();
  SandboxFileSystemBackendDelegate* sandbox_delegate =
      fs_context->sandbox_delegate();

  GURL extension_url =
      extensions::Extension::GetBaseURLFromExtensionId(extension->id());

  scoped_ptr<storage::SandboxFileSystemBackendDelegate::OriginEnumerator>
      enumerator(old_sandbox_delegate->CreateOriginEnumerator());

  // Find out if there is a file system that needs migration.
  GURL origin;
  do {
    origin = enumerator->Next();
  } while (origin != extension_url && !origin.is_empty());

  if (!origin.is_empty()) {
    // Copy the temporary file system.
    if (enumerator->HasFileSystemType(storage::kFileSystemTypeTemporary)) {
      old_sandbox_delegate->CopyFileSystem(
          extension_url, storage::kFileSystemTypeTemporary, sandbox_delegate);
    }
    // Copy the persistent file system.
    if (enumerator->HasFileSystemType(storage::kFileSystemTypePersistent)) {
      old_sandbox_delegate->CopyFileSystem(
          extension_url, storage::kFileSystemTypePersistent, sandbox_delegate);
    }
  }
}

void MigrateOnIndexedDBThread(IndexedDBContext* old_indexed_db_context,
                              IndexedDBContext* indexed_db_context,
                              const extensions::Extension* extension) {
  DCHECK(old_indexed_db_context->TaskRunner()->RunsTasksOnCurrentThread());

  GURL extension_url =
      extensions::Extension::GetBaseURLFromExtensionId(extension->id());

  old_indexed_db_context->CopyOriginData(extension_url, indexed_db_context);
}

void MigrateFileSystem(WeakPtr<extensions::AppDataMigrator> migrator,
                       StoragePartition* old_partition,
                       StoragePartition* current_partition,
                       const extensions::Extension* extension,
                       const base::Closure& reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  // Since this method is static and it's being run as a closure task, check to
  // make sure the calling object is still around.
  if (!migrator.get()) {
    return;
  }

  FileSystemContext* old_fs_context = old_partition->GetFileSystemContext();
  FileSystemContext* fs_context = current_partition->GetFileSystemContext();

  // Perform the file system migration on the old file system's
  // sequenced task runner. This is to ensure it queues after any
  // in-flight file system operations. After it completes, it should
  // invoke the original callback passed into DoMigrationAndReply.
  old_fs_context->default_file_task_runner()->PostTaskAndReply(
      FROM_HERE,
      base::Bind(&MigrateOnFileSystemThread, make_scoped_refptr(old_fs_context),
                 make_scoped_refptr(fs_context), make_scoped_refptr(extension)),
      reply);
}

void MigrateLegacyPartition(WeakPtr<extensions::AppDataMigrator> migrator,
                            StoragePartition* old_partition,
                            StoragePartition* current_partition,
                            const extensions::Extension* extension,
                            const base::Closure& reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  IndexedDBContext* indexed_db_context =
      current_partition->GetIndexedDBContext();
  IndexedDBContext* old_indexed_db_context =
      old_partition->GetIndexedDBContext();

  // Create a closure for the file system migration. This is the next step in
  // the migration flow after the IndexedDB migration.
  base::Closure migrate_fs =
      base::Bind(&MigrateFileSystem, migrator, old_partition, current_partition,
                 make_scoped_refptr(extension), reply);

  // Perform the IndexedDB migration on the old context's sequenced task
  // runner. After completion, it should call MigrateFileSystem.
  old_indexed_db_context->TaskRunner()->PostTaskAndReply(
      FROM_HERE, base::Bind(&MigrateOnIndexedDBThread,
                            make_scoped_refptr(old_indexed_db_context),
                            make_scoped_refptr(indexed_db_context),
                            make_scoped_refptr(extension)),
      migrate_fs);
}

}  // namespace

namespace extensions {

AppDataMigrator::AppDataMigrator(Profile* profile, ExtensionRegistry* registry)
    : profile_(profile), registry_(registry), weak_factory_(this) {
}

AppDataMigrator::~AppDataMigrator() {
}

bool AppDataMigrator::NeedsMigration(const Extension* old,
                                     const Extension* extension) {
  return old && old->is_legacy_packaged_app() && extension->is_platform_app();
}

void AppDataMigrator::DoMigrationAndReply(const Extension* old,
                                          const Extension* extension,
                                          const base::Closure& reply) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(NeedsMigration(old, extension));

  // This should retrieve the general storage partition.
  content::StoragePartition* old_partition =
      BrowserContext::GetStoragePartitionForSite(
          profile_, Extension::GetBaseURLFromExtensionId(extension->id()));

  // Enable the new extension so we can access its storage partition.
  bool old_was_disabled = registry_->AddEnabled(extension);

  // This should create a new isolated partition for the new version of the
  // extension.
  StoragePartition* new_partition = BrowserContext::GetStoragePartitionForSite(
      profile_, Extension::GetBaseURLFromExtensionId(extension->id()));

  // Now, restore the enabled/disabled state of the new and old extensions.
  if (old_was_disabled)
    registry_->RemoveEnabled(extension->id());
  else
    registry_->AddEnabled(old);

  MigrateLegacyPartition(weak_factory_.GetWeakPtr(), old_partition,
                         new_partition, extension, reply);
}

}  // namespace extensions
