chromium/chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/file_system_provider/content_cache/cache_manager_impl.h"

#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/file_system_provider/content_cache/cache_manager.h"
#include "chrome/browser/ash/file_system_provider/content_cache/content_cache_impl.h"
#include "chrome/browser/ash/file_system_provider/content_cache/context_database.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"

namespace ash::file_system_provider {

namespace {

base::File::Error CreateProviderDirectory(const base::FilePath& path) {
  base::File::Error error = base::File::FILE_ERROR_FAILED;
  if (!base::CreateDirectoryAndGetError(path, &error)) {
    return error;
  }

  return base::File::FILE_OK;
}

OptionalContextDatabase InitializeContextDatabase(
    const base::FilePath& db_path) {
  std::unique_ptr<ContextDatabase> context_db =
      std::make_unique<ContextDatabase>(db_path);
  if (context_db->Initialize()) {
    return context_db;
  }
  return std::nullopt;
}

}  // namespace

CacheManagerImpl::CacheManagerImpl(const base::FilePath& profile_path)
    : root_content_cache_directory_(
          profile_path.Append(kFspContentCacheDirName)) {}

CacheManagerImpl::~CacheManagerImpl() = default;

std::unique_ptr<CacheManager> CacheManagerImpl::Create(
    const base::FilePath& profile_path) {
  return std::make_unique<CacheManagerImpl>(profile_path);
}

void CacheManagerImpl::InitializeForProvider(
    const ProvidedFileSystemInfo& file_system_info,
    FileErrorOrContentCacheCallback callback) {
  const base::FilePath cache_directory_path =
      GetCacheDirectoryPath(file_system_info);
  if (cache_directory_path.empty()) {
    std::move(callback).Run(
        base::unexpected(base::File::FILE_ERROR_INVALID_URL));
    return;
  }

  blocking_task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&CreateProviderDirectory, cache_directory_path),
      base::BindOnce(&CacheManagerImpl::OnProviderDirectoryCreationComplete,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                     cache_directory_path));
}

void CacheManagerImpl::UninitializeForProvider(
    const ProvidedFileSystemInfo& file_system_info) {
  const base::FilePath cache_directory_path =
      GetCacheDirectoryPath(file_system_info);
  if (cache_directory_path.empty()) {
    return;
  }
  const base::FilePath base64_encoded_provider_folder_name =
      cache_directory_path.BaseName();
  if (!initialized_providers_.contains(base64_encoded_provider_folder_name)) {
    OnUninitializeForProvider(base64_encoded_provider_folder_name,
                              base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  // Remove the provider from the set.
  initialized_providers_.erase(base64_encoded_provider_folder_name);

  // Attempt to delete the cache directory to ensure dead files don't remain
  // on the user's disk as the logic changes in this experimental design phase.
  blocking_task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(
          [](const base::FilePath& cache_directory_path) {
            return base::DeletePathRecursively(cache_directory_path)
                       ? base::File::FILE_OK
                       : base::File::FILE_ERROR_FAILED;
          },
          std::move(cache_directory_path)),
      base::BindOnce(&CacheManagerImpl::OnUninitializeForProvider,
                     weak_ptr_factory_.GetWeakPtr(),
                     base64_encoded_provider_folder_name));
}

bool CacheManagerImpl::IsProviderInitialized(
    const ProvidedFileSystemInfo& file_system_info) {
  const base::FilePath cache_directory_path =
      GetCacheDirectoryPath(file_system_info);
  if (cache_directory_path.empty()) {
    return false;
  }
  const base::FilePath base64_encoded_provider_folder_name =
      cache_directory_path.BaseName();
  return initialized_providers_.contains(base64_encoded_provider_folder_name);
}

void CacheManagerImpl::AddObserver(Observer* observer) {
  DCHECK(observer);
  observers_.AddObserver(observer);
}

void CacheManagerImpl::RemoveObserver(Observer* observer) {
  DCHECK(observer);
  observers_.RemoveObserver(observer);
}

void CacheManagerImpl::OnProviderDirectoryCreationComplete(
    FileErrorOrContentCacheCallback callback,
    base::FilePath cache_directory_path,
    base::File::Error result) {
  if (result != base::File::FILE_OK) {
    OnProviderInitializationComplete(cache_directory_path.BaseName(),
                                     std::move(callback),
                                     base::unexpected(result));
    return;
  }

  // Initialize the database task runner, the `ContextDatabase` will be created
  // on this task runner and bound to the task runner on return.
  scoped_refptr<base::SequencedTaskRunner> db_task_runner =
      base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
           base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
  db_task_runner->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&InitializeContextDatabase,
                     cache_directory_path.Append("context.db")),
      base::BindOnce(&CacheManagerImpl::OnProviderContextDatabaseSetup,
                     weak_ptr_factory_.GetWeakPtr(), cache_directory_path,
                     std::move(callback), db_task_runner));
}

void CacheManagerImpl::OnProviderContextDatabaseSetup(
    const base::FilePath& cache_directory_path,
    FileErrorOrContentCacheCallback callback,
    scoped_refptr<base::SequencedTaskRunner> db_task_runner,
    OptionalContextDatabase optional_context_db) {
  const base::FilePath base64_encoded_provider_folder_name =
      cache_directory_path.BaseName();

  if (!optional_context_db.has_value()) {
    OnProviderInitializationComplete(
        base64_encoded_provider_folder_name, std::move(callback),
        base::unexpected(base::File::FILE_ERROR_FAILED));
    return;
  }

  // Bind the `ContextDatabase` to the database task runner to ensure
  // sequenced access via the `ContentCache` instance.
  BoundContextDatabase context_db(db_task_runner,
                                  std::move(optional_context_db.value()));
  std::unique_ptr<ContentCache> content_cache =
      ContentCacheImpl::Create(cache_directory_path, std::move(context_db));
  content_cache->LoadFromDisk(
      base::BindOnce(&CacheManagerImpl::OnProviderFilesLoadedFromDisk,
                     weak_ptr_factory_.GetWeakPtr(), std::move(content_cache),
                     base64_encoded_provider_folder_name, std::move(callback)));
}

void CacheManagerImpl::OnProviderFilesLoadedFromDisk(
    std::unique_ptr<ContentCache> content_cache,
    const base::FilePath& base64_encoded_provider_folder_name,
    FileErrorOrContentCacheCallback callback) {
  initialized_providers_.emplace(base64_encoded_provider_folder_name);
  OnProviderInitializationComplete(base64_encoded_provider_folder_name,
                                   std::move(callback),
                                   std::move(content_cache));
}
void CacheManagerImpl::OnUninitializeForProvider(
    const base::FilePath& base64_encoded_provider_folder_name,
    base::File::Error result) {
  LOG_IF(ERROR, result != base::File::FILE_OK)
      << "Failed to uninitialize provider";
  // Notify all observers.
  for (auto& observer : observers_) {
    observer.OnProviderUninitialized(base64_encoded_provider_folder_name,
                                     result);
  }
}

const base::FilePath CacheManagerImpl::GetCacheDirectoryPath(
    const ProvidedFileSystemInfo& file_system_info) {
  const base::FilePath& provider_folder_name =
      file_system_info.mount_path().BaseName();
  if (provider_folder_name.empty()) {
    return base::FilePath();
  }
  // The provider folder name takes the form
  // {provider-id}:{file-system-id}:{user-hash} with the {file-system-id} being
  // escaped but ultimately provided by the extension, so let's convert it to
  // base64 before creating a directory.
  const base::FilePath base64_encoded_provider_folder_name(
      base::Base64Encode(provider_folder_name.value()));

  const base::FilePath cache_directory_path(
      root_content_cache_directory_.Append(
          base64_encoded_provider_folder_name));

  return cache_directory_path;
}

void CacheManagerImpl::OnProviderInitializationComplete(
    const base::FilePath& base64_encoded_provider_folder_name,
    FileErrorOrContentCacheCallback callback,
    FileErrorOrContentCache error_or_content_cache) {
  base::File::Error result =
      error_or_content_cache.error_or(base::File::FILE_OK);
  std::move(callback).Run(std::move(error_or_content_cache));

  for (Observer& observer : observers_) {
    observer.OnProviderInitializationComplete(
        base64_encoded_provider_folder_name, result);
  }
}

}  // namespace ash::file_system_provider