text: // 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 "services/catalog/catalog.h"
  
  #include <memory>
  #include <string>
  
  #include "base/base_paths.h"
  #include "base/command_line.h"
  #include "base/files/file_path.h"
  #include "base/files/file_util.h"
  #include "base/json/json_reader.h"
  #include "base/lazy_instance.h"
  #include "base/macros.h"
  #include "base/memory/ptr_util.h"
  #include "base/path_service.h"
  #include "base/strings/string_util.h"
  #include "base/strings/utf_string_conversions.h"
  #include "components/filesystem/directory_impl.h"
  #include "components/filesystem/lock_table.h"
  #include "components/filesystem/public/interfaces/types.mojom.h"
  #include "mojo/public/cpp/bindings/strong_binding.h"
  #include "services/catalog/constants.h"
  #include "services/catalog/entry_cache.h"
  #include "services/catalog/instance.h"
  #include "services/service_manager/public/cpp/binder_registry.h"
  #include "services/service_manager/public/cpp/service_context.h"
  
  namespace catalog {
  
  namespace {
  
  const char kCatalogServicesKey[] = "services";
  const char kCatalogServiceEmbeddedKey[] = "embedded";
  const char kCatalogServiceExecutableKey[] = "executable";
  const char kCatalogServiceManifestKey[] = "manifest";
  
  base::LazyInstance<std::unique_ptr<base::Value>>::DestructorAtExit
      g_default_static_manifest = LAZY_INSTANCE_INITIALIZER;
  
  void LoadCatalogManifestIntoCache(const base::Value* root, EntryCache* cache) {
    DCHECK(root);
    const base::DictionaryValue* catalog = nullptr;
    if (!root->GetAsDictionary(&catalog)) {
      LOG(ERROR) << "Catalog manifest is not a dictionary value.";
      return;
    }
    DCHECK(catalog);
  
    const base::DictionaryValue* services = nullptr;
    if (!catalog->GetDictionary(kCatalogServicesKey, &services)) {
      LOG(ERROR) << "Catalog manifest \"services\" is not a dictionary value.";
      return;
    }
  
    for (base::DictionaryValue::Iterator it(*services); !it.IsAtEnd();
         it.Advance()) {
      const base::DictionaryValue* service_entry = nullptr;
      if (!it.value().GetAsDictionary(&service_entry)) {
        LOG(ERROR) << "Catalog service entry for \"" << it.key()
                   << "\" is not a dictionary value.";
        continue;
      }
  
      bool is_embedded = false;
      service_entry->GetBoolean(kCatalogServiceEmbeddedKey, &is_embedded);
  
      base::FilePath executable_path;
      std::string executable_path_string;
      if (service_entry->GetString(kCatalogServiceExecutableKey,
                                   &executable_path_string)) {
        base::FilePath exe_dir;
        CHECK(base::PathService::Get(base::DIR_EXE, &exe_dir));
  #if defined(OS_WIN)
        executable_path_string += ".exe";
        base::ReplaceFirstSubstringAfterOffset(
            &executable_path_string, 0, "@EXE_DIR",
            base::UTF16ToUTF8(exe_dir.value()));
        executable_path =
            base::FilePath(base::UTF8ToUTF16(executable_path_string));
  #else
        base::ReplaceFirstSubstringAfterOffset(
            &executable_path_string, 0, "@EXE_DIR", exe_dir.value());
        executable_path = base::FilePath(executable_path_string);
  #endif
      }
  
      const base::DictionaryValue* manifest = nullptr;
      if (!service_entry->GetDictionary(kCatalogServiceManifestKey, &manifest)) {
        LOG(ERROR) << "Catalog entry for \"" << it.key() << "\" has an invalid "
                   << "\"manifest\" value.";
        continue;
      }
  
      DCHECK(!(is_embedded && !executable_path.empty()));
  
      if (is_embedded)
        executable_path = base::CommandLine::ForCurrentProcess()->GetProgram();
  
      auto entry = Entry::Deserialize(*manifest);
      if (entry) {
        if (!executable_path.empty())
          entry->set_path(std::move(executable_path));
        bool added = cache->AddRootEntry(std::move(entry));
        DCHECK(added);
      } else {
        LOG(ERROR) << "Failed to read manifest entry for \"" << it.key() << "\".";
      }
    }
  }
  
  }  // namespace
  
  class Catalog::ServiceImpl : public service_manager::Service {
   public:
    explicit ServiceImpl(Catalog* catalog) : catalog_(catalog) {
      registry_.AddInterface<mojom::Catalog>(
          base::Bind(&Catalog::BindCatalogRequest, base::Unretained(catalog_)));
      registry_.AddInterface<filesystem::mojom::Directory>(
          base::Bind(&Catalog::BindDirectoryRequest, base::Unretained(catalog_)));
    }
    ~ServiceImpl() override {}
  
    // service_manager::Service:
    void OnBindInterface(const service_manager::BindSourceInfo& source_info,
                         const std::string& interface_name,
                         mojo::ScopedMessagePipeHandle interface_pipe) override {
      registry_.BindInterface(interface_name, std::move(interface_pipe),
                              source_info);
    }
  
   private:
    Catalog* const catalog_;
    service_manager::BinderRegistryWithArgs<
        const service_manager::BindSourceInfo&>
        registry_;
  
    DISALLOW_COPY_AND_ASSIGN(ServiceImpl);
  };
  
  Catalog::Catalog(std::unique_ptr<base::Value> static_manifest,
                   ManifestProvider* service_manifest_provider)
      : service_context_(new service_manager::ServiceContext(
            base::MakeUnique<ServiceImpl>(this),
            mojo::MakeRequest(&service_))),
        service_manifest_provider_(service_manifest_provider),
        weak_factory_(this) {
    if (static_manifest) {
      LoadCatalogManifestIntoCache(static_manifest.get(), &system_cache_);
    } else if (g_default_static_manifest.Get()) {
      LoadCatalogManifestIntoCache(
          g_default_static_manifest.Get().get(), &system_cache_);
    }
  }
  
  Catalog::~Catalog() {}
  
  service_manager::mojom::ServicePtr Catalog::TakeService() {
    return std::move(service_);
  }
  
  // static
  void Catalog::SetDefaultCatalogManifest(
      std::unique_ptr<base::Value> static_manifest) {
    g_default_static_manifest.Get() = std::move(static_manifest);
  }
  
  // static
  void Catalog::LoadDefaultCatalogManifest(const base::FilePath& path) {
    std::string catalog_contents;
    base::FilePath exe_path;
    base::PathService::Get(base::DIR_EXE, &exe_path);
    base::FilePath catalog_path = exe_path.Append(path);
    bool result = base::ReadFileToString(catalog_path, &catalog_contents);
    DCHECK(result);
    std::unique_ptr<base::Value> manifest_value =
        base::JSONReader::Read(catalog_contents);
    DCHECK(manifest_value);
    catalog::Catalog::SetDefaultCatalogManifest(std::move(manifest_value));
  }
  
  Instance* Catalog::GetInstanceForUserId(const std::string& user_id) {
    auto it = instances_.find(user_id);
    if (it != instances_.end())
      return it->second.get();
  
    auto result = instances_.insert(std::make_pair(
        user_id,
        base::MakeUnique<Instance>(&system_cache_, service_manifest_provider_)));
    return result.first->second.get();
  }
  
  void Catalog::BindCatalogRequest(
      mojom::CatalogRequest request,
      const service_manager::BindSourceInfo& source_info) {
    Instance* instance = GetInstanceForUserId(source_info.identity.user_id());
    instance->BindCatalog(std::move(request));
  }
  
  void Catalog::BindDirectoryRequest(
      filesystem::mojom::DirectoryRequest request,
      const service_manager::BindSourceInfo& source_info) {
    if (!lock_table_)
      lock_table_ = new filesystem::LockTable;
  
    base::FilePath resources_path;
    base::PathService::Get(base::DIR_MODULE, &resources_path);
    mojo::MakeStrongBinding(
        base::MakeUnique<filesystem::DirectoryImpl>(
            resources_path, scoped_refptr<filesystem::SharedTempDir>(),
            lock_table_),
        std::move(request));
  }
  
  }  // namespace catalog
  