// Copyright 2016 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 "extensions/browser/value_store/legacy_value_store_factory.h"

#include <string>

#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "extensions/browser/value_store/leveldb_value_store.h"
#include "extensions/common/constants.h"

using base::AutoLock;

namespace {

// Statistics are logged to UMA with these strings as part of histogram name.
// They can all be found under Extensions.Database.Open.<client>. Changing this
// needs to synchronize with histograms.xml, AND will also become incompatible
// with older browsers still reporting the previous values.
const char kSettingsDatabaseUMAClientName[] = "Settings";
const char kRulesDatabaseUMAClientName[] = "Rules";
const char kStateDatabaseUMAClientName[] = "State";

bool ValidDBExists(const base::FilePath& path) {
  // TODO(cmumford): Enhance to detect if dir contains valid database.
  return base::DirectoryExists(path);
}

}  // namespace

namespace extensions {

//
// ModelSettings
//
LegacyValueStoreFactory::ModelSettings::ModelSettings(
    const base::FilePath& data_path)
    : data_path_(data_path) {}

base::FilePath LegacyValueStoreFactory::ModelSettings::GetDBPath(
    const ExtensionId& extension_id) const {
  return data_path_.AppendASCII(extension_id);
}

bool LegacyValueStoreFactory::ModelSettings::DeleteData(
    const ExtensionId& extension_id) {
  return base::DeletePathRecursively(GetDBPath(extension_id));
}

bool LegacyValueStoreFactory::ModelSettings::DataExists(
    const ExtensionId& extension_id) const {
  return ValidDBExists(GetDBPath(extension_id));
}

std::set<ExtensionId>
LegacyValueStoreFactory::ModelSettings::GetKnownExtensionIDs() const {
  std::set<ExtensionId> result;

  // Leveldb databases are directories inside |base_path_|.
  base::FileEnumerator extension_dirs(data_path_, false,
                                      base::FileEnumerator::DIRECTORIES);
  while (!extension_dirs.Next().empty()) {
    base::FilePath extension_dir = extension_dirs.GetInfo().GetName();
    DCHECK(!extension_dir.IsAbsolute());
    // Extension ID's are 'a'..'p', so any directory within this folder will
    // either be ASCII, or created by some other application and safe to ignore.
    std::string maybe_as_ascii(extension_dir.MaybeAsASCII());
    if (!maybe_as_ascii.empty()) {
      result.insert(maybe_as_ascii);
    }
  }

  return result;
}

//
// SettingsRoot
//

LegacyValueStoreFactory::SettingsRoot::SettingsRoot(
    const base::FilePath& base_path,
    const std::string& extension_dirname,
    const std::string& app_dirname) {
  if (!extension_dirname.empty())
    extensions_.reset(
        new ModelSettings(base_path.AppendASCII(extension_dirname)));

  if (!app_dirname.empty())
    apps_.reset(new ModelSettings(base_path.AppendASCII(app_dirname)));
}

LegacyValueStoreFactory::SettingsRoot::~SettingsRoot() = default;

const LegacyValueStoreFactory::ModelSettings*
LegacyValueStoreFactory::SettingsRoot::GetModel(ModelType model_type) const {
  switch (model_type) {
    case ValueStoreFactory::ModelType::APP:
      DCHECK(apps_ != nullptr);
      return apps_.get();
    case ValueStoreFactory::ModelType::EXTENSION:
      DCHECK(extensions_ != nullptr);
      return extensions_.get();
  }
  NOTREACHED();
  return nullptr;
}

LegacyValueStoreFactory::ModelSettings*
LegacyValueStoreFactory::SettingsRoot::GetModel(ModelType model_type) {
  switch (model_type) {
    case ValueStoreFactory::ModelType::APP:
      DCHECK(apps_ != nullptr);
      return apps_.get();
    case ValueStoreFactory::ModelType::EXTENSION:
      DCHECK(extensions_ != nullptr);
      return extensions_.get();
  }
  NOTREACHED();
  return nullptr;
}

std::set<ExtensionId>
LegacyValueStoreFactory::SettingsRoot::GetKnownExtensionIDs(
    ModelType model_type) const {
  switch (model_type) {
    case ValueStoreFactory::ModelType::APP:
      DCHECK(apps_ != nullptr);
      return apps_->GetKnownExtensionIDs();
    case ValueStoreFactory::ModelType::EXTENSION:
      DCHECK(extensions_ != nullptr);
      return extensions_->GetKnownExtensionIDs();
  }
  NOTREACHED();
  return std::set<ExtensionId>();
}

//
// LegacyValueStoreFactory
//

LegacyValueStoreFactory::LegacyValueStoreFactory(
    const base::FilePath& profile_path)
    : profile_path_(profile_path),
      local_settings_(profile_path,
                      kLocalExtensionSettingsDirectoryName,
                      kLocalAppSettingsDirectoryName),
      sync_settings_(profile_path,
                     kSyncExtensionSettingsDirectoryName,
                     kSyncAppSettingsDirectoryName),
      // Currently no such thing as a managed app - only an extension.
      managed_settings_(profile_path, kManagedSettingsDirectoryName, "") {}

LegacyValueStoreFactory::~LegacyValueStoreFactory() = default;

bool LegacyValueStoreFactory::RulesDBExists() const {
  return ValidDBExists(GetRulesDBPath());
}

bool LegacyValueStoreFactory::StateDBExists() const {
  return ValidDBExists(GetStateDBPath());
}

std::unique_ptr<ValueStore> LegacyValueStoreFactory::CreateRulesStore() {
  return std::make_unique<LeveldbValueStore>(kRulesDatabaseUMAClientName,
                                             GetRulesDBPath());
}

std::unique_ptr<ValueStore> LegacyValueStoreFactory::CreateStateStore() {
  return std::make_unique<LeveldbValueStore>(kStateDatabaseUMAClientName,
                                             GetStateDBPath());
}

std::unique_ptr<ValueStore> LegacyValueStoreFactory::CreateSettingsStore(
    settings_namespace::Namespace settings_namespace,
    ModelType model_type,
    const ExtensionId& extension_id) {
  const ModelSettings* settings_root =
      GetSettingsRoot(settings_namespace).GetModel(model_type);
  DCHECK(settings_root != nullptr);
  return std::make_unique<LeveldbValueStore>(
      kSettingsDatabaseUMAClientName, settings_root->GetDBPath(extension_id));
}

void LegacyValueStoreFactory::DeleteSettings(
    settings_namespace::Namespace settings_namespace,
    ModelType model_type,
    const ExtensionId& extension_id) {
  ModelSettings* model_settings =
      GetSettingsRoot(settings_namespace).GetModel(model_type);
  if (model_settings == nullptr) {
    NOTREACHED();
    return;
  }
  model_settings->DeleteData(extension_id);
}

bool LegacyValueStoreFactory::HasSettings(
    settings_namespace::Namespace settings_namespace,
    ModelType model_type,
    const ExtensionId& extension_id) {
  const ModelSettings* model_settings =
      GetSettingsRoot(settings_namespace).GetModel(model_type);
  if (model_settings == nullptr)
    return false;
  return model_settings->DataExists(extension_id);
}

std::set<ExtensionId> LegacyValueStoreFactory::GetKnownExtensionIDs(
    settings_namespace::Namespace settings_type,
    ModelType model_type) const {
  return GetSettingsRoot(settings_type).GetKnownExtensionIDs(model_type);
}

const LegacyValueStoreFactory::SettingsRoot&
LegacyValueStoreFactory::GetSettingsRoot(
    settings_namespace::Namespace settings_namespace) const {
  switch (settings_namespace) {
    case settings_namespace::LOCAL:
      return local_settings_;
    case settings_namespace::SYNC:
      return sync_settings_;
    case settings_namespace::MANAGED:
      return managed_settings_;
    case settings_namespace::INVALID:
      break;
  }
  NOTREACHED();
  return local_settings_;
}

LegacyValueStoreFactory::SettingsRoot& LegacyValueStoreFactory::GetSettingsRoot(
    settings_namespace::Namespace settings_namespace) {
  switch (settings_namespace) {
    case settings_namespace::LOCAL:
      return local_settings_;
    case settings_namespace::SYNC:
      return sync_settings_;
    case settings_namespace::MANAGED:
      return managed_settings_;
    case settings_namespace::INVALID:
      break;
  }
  NOTREACHED();
  return local_settings_;
}

base::FilePath LegacyValueStoreFactory::GetRulesDBPath() const {
  return profile_path_.AppendASCII(kRulesStoreName);
}

base::FilePath LegacyValueStoreFactory::GetStateDBPath() const {
  return profile_path_.AppendASCII(kStateStoreName);
}

}  // namespace extensions
