// Copyright (c) 2012 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 "webkit/fileapi/file_system_context.h"

#include "base/bind.h"
#include "base/file_util.h"
#include "base/stl_util.h"
#include "base/single_thread_task_runner.h"
#include "googleurl/src/gurl.h"
#include "webkit/fileapi/file_system_file_util.h"
#include "webkit/fileapi/file_system_operation.h"
#include "webkit/fileapi/file_system_options.h"
#include "webkit/fileapi/file_system_quota_client.h"
#include "webkit/fileapi/file_system_task_runners.h"
#include "webkit/fileapi/file_system_url.h"
#include "webkit/fileapi/file_system_util.h"
#include "webkit/fileapi/isolated_context.h"
#include "webkit/fileapi/isolated_mount_point_provider.h"
#include "webkit/fileapi/sandbox_mount_point_provider.h"
#include "webkit/fileapi/syncable/local_file_change_tracker.h"
#include "webkit/fileapi/syncable/local_file_sync_context.h"
#include "webkit/fileapi/syncable/syncable_file_system_util.h"
#include "webkit/fileapi/test_mount_point_provider.h"
#include "webkit/quota/quota_manager.h"
#include "webkit/quota/special_storage_policy.h"

#if defined(OS_CHROMEOS)
#include "webkit/chromeos/fileapi/cros_mount_point_provider.h"
#endif

using quota::QuotaClient;

namespace fileapi {

namespace {

QuotaClient* CreateQuotaClient(
    FileSystemContext* context,
    bool is_incognito) {
  return new FileSystemQuotaClient(context, is_incognito);
}

void DidOpenFileSystem(
    const FileSystemContext::OpenFileSystemCallback& callback,
    const GURL& filesystem_root,
    const std::string& filesystem_name,
    base::PlatformFileError error) {
  callback.Run(error, filesystem_name, filesystem_root);
}

}  // namespace

FileSystemContext::FileSystemContext(
    scoped_ptr<FileSystemTaskRunners> task_runners,
    quota::SpecialStoragePolicy* special_storage_policy,
    quota::QuotaManagerProxy* quota_manager_proxy,
    const FilePath& partition_path,
    const FileSystemOptions& options)
    : task_runners_(task_runners.Pass()),
      quota_manager_proxy_(quota_manager_proxy),
      sandbox_provider_(
          new SandboxMountPointProvider(
              quota_manager_proxy,
              task_runners_->file_task_runner(),
              partition_path,
              options)),
      isolated_provider_(new IsolatedMountPointProvider(partition_path)),
      partition_path_(partition_path) {
  DCHECK(task_runners_.get());

  if (quota_manager_proxy) {
    quota_manager_proxy->RegisterClient(CreateQuotaClient(
            this, options.is_incognito()));
  }
#if defined(OS_CHROMEOS)
  external_provider_.reset(
      new chromeos::CrosMountPointProvider(special_storage_policy));
#endif
}

bool FileSystemContext::DeleteDataForOriginOnFileThread(
    const GURL& origin_url) {
  DCHECK(task_runners_->file_task_runner()->RunsTasksOnCurrentThread());
  DCHECK(sandbox_provider());
  DCHECK(origin_url == origin_url.GetOrigin());

  // Delete temporary and persistent data.
  return
      (sandbox_provider()->DeleteOriginDataOnFileThread(
          this, quota_manager_proxy(), origin_url,
          kFileSystemTypeTemporary) ==
       base::PLATFORM_FILE_OK) &&
      (sandbox_provider()->DeleteOriginDataOnFileThread(
          this, quota_manager_proxy(), origin_url,
          kFileSystemTypePersistent) ==
       base::PLATFORM_FILE_OK) &&
      (sandbox_provider()->DeleteOriginDataOnFileThread(
          this, quota_manager_proxy(), origin_url,
          kFileSystemTypeSyncable) ==
       base::PLATFORM_FILE_OK);
}

FileSystemQuotaUtil*
FileSystemContext::GetQuotaUtil(FileSystemType type) const {
  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(type);
  if (!mount_point_provider)
    return NULL;
  return mount_point_provider->GetQuotaUtil();
}

FileSystemFileUtil* FileSystemContext::GetFileUtil(
    FileSystemType type) const {
  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(type);
  if (!mount_point_provider)
    return NULL;
  return mount_point_provider->GetFileUtil(type);
}

FileSystemMountPointProvider* FileSystemContext::GetMountPointProvider(
    FileSystemType type) const {
  switch (type) {
    case kFileSystemTypeTemporary:
    case kFileSystemTypePersistent:
    case kFileSystemTypeSyncable:
      return sandbox_provider_.get();
    case kFileSystemTypeExternal:
    case kFileSystemTypeDrive:
    case kFileSystemTypeRestrictedNativeLocal:
      return external_provider_.get();
    case kFileSystemTypeIsolated:
    case kFileSystemTypeDragged:
    case kFileSystemTypeNativeMedia:
    case kFileSystemTypeDeviceMedia:
      return isolated_provider_.get();
    case kFileSystemTypeNativeLocal:
#if defined(OS_CHROMEOS)
      return external_provider_.get();
#else
      return isolated_provider_.get();
#endif
    default:
      if (provider_map_.find(type) != provider_map_.end())
        return provider_map_.find(type)->second;
      // Fall through.
    case kFileSystemTypeUnknown:
      NOTREACHED();
      return NULL;
  }
}

const UpdateObserverList* FileSystemContext::GetUpdateObservers(
    FileSystemType type) const {
  // Currently update observer is only available in SandboxMountPointProvider
  // and TestMountPointProvider.
  // TODO(kinuko): Probably GetUpdateObservers() virtual method should be
  // added to FileSystemMountPointProvider interface and be called like
  // other GetFoo() methods do.
  if (SandboxMountPointProvider::CanHandleType(type))
    return sandbox_provider()->GetUpdateObservers(type);
  if (type != kFileSystemTypeTest)
    return NULL;
  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(type);
  return static_cast<TestMountPointProvider*>(
      mount_point_provider)->GetUpdateObservers(type);
}

SandboxMountPointProvider*
FileSystemContext::sandbox_provider() const {
  return sandbox_provider_.get();
}

ExternalFileSystemMountPointProvider*
FileSystemContext::external_provider() const {
  return external_provider_.get();
}

void FileSystemContext::OpenFileSystem(
    const GURL& origin_url,
    FileSystemType type,
    bool create,
    const OpenFileSystemCallback& callback) {
  DCHECK(!callback.is_null());

  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(type);
  if (!mount_point_provider) {
    callback.Run(base::PLATFORM_FILE_ERROR_SECURITY, std::string(), GURL());
    return;
  }

  GURL root_url = GetFileSystemRootURI(origin_url, type);
  std::string name = GetFileSystemName(origin_url, type);

  mount_point_provider->ValidateFileSystemRoot(
      origin_url, type, create,
      base::Bind(&DidOpenFileSystem, callback, root_url, name));
}

void FileSystemContext::OpenSyncableFileSystem(
    const std::string& mount_name,
    const GURL& origin_url,
    FileSystemType type,
    bool create,
    const OpenFileSystemCallback& callback) {
  DCHECK(!callback.is_null());

  DCHECK(type == kFileSystemTypeSyncable);

  GURL root_url = GetSyncableFileSystemRootURI(origin_url, mount_name);
  std::string name = GetFileSystemName(origin_url, kFileSystemTypeSyncable);

  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(type);
  DCHECK(mount_point_provider);
  mount_point_provider->ValidateFileSystemRoot(
      origin_url, type, create,
      base::Bind(&DidOpenFileSystem, callback, root_url, name));
}

void FileSystemContext::DeleteFileSystem(
    const GURL& origin_url,
    FileSystemType type,
    const DeleteFileSystemCallback& callback) {
  DCHECK(origin_url == origin_url.GetOrigin());
  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(type);
  if (!mount_point_provider) {
    callback.Run(base::PLATFORM_FILE_ERROR_SECURITY);
    return;
  }

  mount_point_provider->DeleteFileSystem(origin_url, type, this, callback);
}

FileSystemOperation* FileSystemContext::CreateFileSystemOperation(
    const FileSystemURL& url, PlatformFileError* error_code) {
  if (!url.is_valid()) {
    if (error_code)
      *error_code = base::PLATFORM_FILE_ERROR_INVALID_URL;
    return NULL;
  }
  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(url.type());
  if (!mount_point_provider) {
    if (error_code)
      *error_code = base::PLATFORM_FILE_ERROR_FAILED;
    return NULL;
  }

  PlatformFileError fs_error = base::PLATFORM_FILE_OK;
  FileSystemOperation* operation =
      mount_point_provider->CreateFileSystemOperation(url, this, &fs_error);

  if (error_code)
    *error_code = fs_error;
  return operation;
}

webkit_blob::FileStreamReader* FileSystemContext::CreateFileStreamReader(
    const FileSystemURL& url,
    int64 offset,
    const base::Time& expected_modification_time) {
  if (!url.is_valid())
    return NULL;
  FileSystemMountPointProvider* mount_point_provider =
      GetMountPointProvider(url.type());
  if (!mount_point_provider)
    return NULL;
  return mount_point_provider->CreateFileStreamReader(
      url, offset, expected_modification_time, this);
}

void FileSystemContext::RegisterMountPointProvider(
    FileSystemType type,
    FileSystemMountPointProvider* provider) {
  DCHECK(provider);
  DCHECK(provider_map_.find(type) == provider_map_.end());
  provider_map_[type] = provider;
}

void FileSystemContext::SetLocalFileChangeTracker(
    scoped_ptr<LocalFileChangeTracker> tracker) {
  DCHECK(!change_tracker_.get());
  DCHECK(tracker.get());
  change_tracker_ = tracker.Pass();
  sandbox_provider_->AddSyncableFileUpdateObserver(
      change_tracker_.get(),
      task_runners_->file_task_runner());
  sandbox_provider_->AddSyncableFileChangeObserver(
      change_tracker_.get(),
      task_runners_->file_task_runner());
}

void FileSystemContext::set_sync_context(
    LocalFileSyncContext* sync_context) {
  sync_context_ = sync_context;
}

FileSystemContext::~FileSystemContext() {
  task_runners_->file_task_runner()->DeleteSoon(
      FROM_HERE, change_tracker_.release());
}

void FileSystemContext::DeleteOnCorrectThread() const {
  if (!task_runners_->io_task_runner()->RunsTasksOnCurrentThread() &&
      task_runners_->io_task_runner()->DeleteSoon(FROM_HERE, this)) {
    return;
  }
  STLDeleteContainerPairSecondPointers(provider_map_.begin(),
                                       provider_map_.end());
  delete this;
}

}  // namespace fileapi