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

#include "net/disk_cache/disk_cache.h"

#include <utility>

#include "base/barrier_closure.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "net/base/cache_type.h"
#include "net/base/features.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/backend_cleanup_tracker.h"
#include "net/disk_cache/basic_cache_file.h"
#include "net/disk_cache/blockfile/backend_impl.h"
#include "net/disk_cache/buildflags.h"
#include "net/disk_cache/cache_encryption_delegate.h"
#include "net/disk_cache/cache_util.h"
#include "net/disk_cache/disk_cache.h"
#include "net/disk_cache/memory/mem_backend_impl.h"
#include "net/disk_cache/simple/simple_backend_impl.h"
#include "net/disk_cache/simple/simple_file_enumerator.h"
#include "net/disk_cache/simple/simple_util.h"
#include "net/disk_cache/trivial_cache_entry_hasher.h"

#if BUILDFLAG(ENABLE_DISK_CACHE_SQL_BACKEND)
#include "net/disk_cache/sql/sql_backend_impl.h"
#endif  // ENABLE_DISK_CACHE_SQL_BACKEND

namespace {

void RecordDiskCacheInitTime(base::TimeDelta time) {
#if !BUILDFLAG(IS_FUCHSIA)
  base::UmaHistogramTimes("HttpCache.TimeToInitDiskCache", time);
#endif  // !BUILDFLAG(IS_FUCHSIA)
}

using FileEnumerator = disk_cache::BackendFileOperations::FileEnumerator;
using ApplicationStatusListenerGetter =
    disk_cache::ApplicationStatusListenerGetter;

// Builds an instance of the backend depending on platform, type, experiments
// etc. Takes care of the retry state. This object will self-destroy when
// finished.
class CacheCreator {
 public:
  CacheCreator(const base::FilePath& path,
               disk_cache::ResetHandling reset_handling,
               int64_t max_bytes,
               net::CacheType type,
               net::BackendType backend_type,
               scoped_refptr<disk_cache::BackendFileOperationsFactory>
                   file_operations_factory,
#if BUILDFLAG(IS_ANDROID)
               ApplicationStatusListenerGetter app_status_listener_getter,
#endif
               net::NetLog* net_log,
               net::CacheEncryptionDelegate* cache_encryption_delegate,
               base::OnceClosure post_cleanup_callback,
               disk_cache::BackendResultCallback callback);

  CacheCreator(const CacheCreator&) = delete;
  CacheCreator& operator=(const CacheCreator&) = delete;

  // Wait for any previous backends for given path to finish clean up and then
  // attempt to create a new one. This is always asynchronous.
  void TryCreateCleanupTrackerAndRun();

  // Queues an asynchronous failure.
  void FailAttempt();

  // Special entry point for DISK_CACHE that initializes encryption
  // but skips the cleanup tracker.
  void InitEncryptionAndRun();

 private:
  ~CacheCreator();

  // Creates the backend, the cleanup context for it having been already
  // established... or purposefully left as null. This is always asynchronous.
  void Run();

  void DoCallback(int result);

  void OnEncryptionInitComplete(net::Error result);
  void OnIOComplete(int result);
  void OnCacheCleanupComplete(int original_error, bool cleanup_result);

  const base::FilePath path_;
  disk_cache::ResetHandling reset_handling_;
  bool retry_ = false;
  int64_t max_bytes_;
  net::CacheType type_;
  net::BackendType backend_type_;
  scoped_refptr<disk_cache::BackendFileOperationsFactory>
      file_operations_factory_;
  std::unique_ptr<disk_cache::BackendFileOperations> file_operations_;
#if BUILDFLAG(IS_ANDROID)
  ApplicationStatusListenerGetter app_status_listener_getter_;
#endif
  base::OnceClosure post_cleanup_callback_;
  disk_cache::BackendResultCallback callback_;
  std::unique_ptr<disk_cache::Backend> created_cache_;
  raw_ptr<net::NetLog> net_log_;
  scoped_refptr<disk_cache::BackendCleanupTracker> cleanup_tracker_;
  raw_ptr<net::CacheEncryptionDelegate> cache_encryption_delegate_;
  base::TimeTicks init_start_time_;
};

CacheCreator::CacheCreator(
    const base::FilePath& path,
    disk_cache::ResetHandling reset_handling,
    int64_t max_bytes,
    net::CacheType type,
    net::BackendType backend_type,
    scoped_refptr<disk_cache::BackendFileOperationsFactory> file_operations,
#if BUILDFLAG(IS_ANDROID)
    ApplicationStatusListenerGetter app_status_listener_getter,
#endif
    net::NetLog* net_log,
    net::CacheEncryptionDelegate* cache_encryption_delegate,
    base::OnceClosure post_cleanup_callback,
    disk_cache::BackendResultCallback callback)
    : path_(path),
      reset_handling_(reset_handling),
      max_bytes_(max_bytes),
      type_(type),
      backend_type_(backend_type),
      file_operations_factory_(std::move(file_operations)),
#if BUILDFLAG(IS_ANDROID)
      app_status_listener_getter_(std::move(app_status_listener_getter)),
#endif
      post_cleanup_callback_(std::move(post_cleanup_callback)),
      callback_(std::move(callback)),
      net_log_(net_log),
      cache_encryption_delegate_(cache_encryption_delegate),
      init_start_time_(base::TimeTicks::Now()) {
}

CacheCreator::~CacheCreator() = default;

void CacheCreator::Run() {
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
  static const bool kSimpleBackendIsDefault = true;
#else
  static const bool kSimpleBackendIsDefault = false;
#endif
  if (!retry_ && reset_handling_ == disk_cache::ResetHandling::kReset) {
    // Pretend that we failed to create a cache, so that we can handle `kReset`
    // and `kResetOnError` in a unified way, in CacheCreator::OnIOComplete.
    FailAttempt();
    return;
  }
  if (backend_type_ == net::CACHE_BACKEND_SIMPLE ||
      (backend_type_ == net::CACHE_BACKEND_DEFAULT &&
       kSimpleBackendIsDefault)) {
    std::unique_ptr<disk_cache::CacheEntryHasher> cache_entry_hasher;
    if (cache_encryption_delegate_) {
      cache_entry_hasher = cache_encryption_delegate_->GetCacheEntryHasher();
      if (!cache_entry_hasher) {
        FailAttempt();
        return;
      }
    } else {
      cache_entry_hasher =
          std::make_unique<disk_cache::TrivialCacheEntryHasher>();
    }
    auto cache = std::make_unique<disk_cache::SimpleBackendImpl>(
        file_operations_factory_, path_, cleanup_tracker_.get(),
        /* file_tracker = */ nullptr, max_bytes_, type_,
        std::move(cache_entry_hasher), net_log_);
    disk_cache::SimpleBackendImpl* simple_cache = cache.get();
    created_cache_ = std::move(cache);
#if BUILDFLAG(IS_ANDROID)
    if (app_status_listener_getter_) {
      simple_cache->set_app_status_listener_getter(app_status_listener_getter_);
    }
#endif
    simple_cache->Init(
        base::BindOnce(&CacheCreator::OnIOComplete, base::Unretained(this)));
    return;
  }

#if BUILDFLAG(ENABLE_DISK_CACHE_SQL_BACKEND)
  if (backend_type_ == net::CACHE_BACKEND_EXPERIMENTAL_SQL) {
    auto sql_cache = std::make_unique<disk_cache::SqlBackendImpl>(
        path_, max_bytes_, type_, cleanup_tracker_);
    auto* sql_cache_ptr = sql_cache.get();
    created_cache_ = std::move(sql_cache);
    sql_cache_ptr->Init(
        base::BindOnce(&CacheCreator::OnIOComplete, base::Unretained(this)));
    return;
  }
#endif  // ENABLE_DISK_CACHE_SQL_BACKEND

// Avoid references to blockfile functions on Android to reduce binary size.
#if BUILDFLAG(IS_ANDROID)
  FailAttempt();
#else
  auto cache = std::make_unique<disk_cache::BackendImpl>(
      path_, cleanup_tracker_.get(),
      /*cache_thread = */ nullptr, type_, net_log_);
  disk_cache::BackendImpl* new_cache = cache.get();
  created_cache_ = std::move(cache);
  if (!new_cache->SetMaxSize(max_bytes_)) {
    FailAttempt();
    return;
  }
  new_cache->Init(
      base::BindOnce(&CacheCreator::OnIOComplete, base::Unretained(this)));
#endif
}

void CacheCreator::FailAttempt() {
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&CacheCreator::OnIOComplete,
                                base::Unretained(this), net::ERR_FAILED));
}

void CacheCreator::InitEncryptionAndRun() {
  if (cache_encryption_delegate_) {
    cache_encryption_delegate_->Init(base::BindOnce(
        &CacheCreator::OnEncryptionInitComplete, base::Unretained(this)));
    return;
  }
  Run();
}

void CacheCreator::TryCreateCleanupTrackerAndRun() {
  // Before creating a cache Backend, a BackendCleanupTracker object is needed
  // so there is a place to keep track of outstanding I/O even after the backend
  // object itself is destroyed, so that further use of the directory
  // doesn't race with those outstanding disk I/O ops.

  // This method's purpose it to grab exclusive ownership of a fresh
  // BackendCleanupTracker for the cache path, and then move on to Run(),
  // which will take care of creating the actual cache backend. It's possible
  // that something else is currently making use of the directory, in which
  // case BackendCleanupTracker::TryCreate will fail, but will just have
  // TryCreateCleanupTrackerAndRun run again at an opportune time to make
  // another attempt.

  // The resulting BackendCleanupTracker is stored into a scoped_refptr member
  // so that it's kept alive while |this| CacheCreator exists , so that in the
  // case Run() needs to retry Backend creation the same BackendCleanupTracker
  // is used for both attempts, and |post_cleanup_callback_| gets called after
  // the second try, not the first one.
  cleanup_tracker_ = disk_cache::BackendCleanupTracker::TryCreate(
      path_, base::BindOnce(base::IgnoreResult(
                                &CacheCreator::TryCreateCleanupTrackerAndRun),
                            base::Unretained(this)));
  if (!cleanup_tracker_) {
    return;
  }
  if (!post_cleanup_callback_.is_null()) {
    cleanup_tracker_->AddPostCleanupCallback(std::move(post_cleanup_callback_));
  }

  if (type_ == net::DISK_CACHE) {
    InitEncryptionAndRun();
  } else {
    Run();
  }
}

void CacheCreator::DoCallback(int net_error) {
  DCHECK_NE(net::ERR_IO_PENDING, net_error);
  disk_cache::BackendResult result;
  if (net_error == net::OK) {
    if (created_cache_->GetCacheType() == net::DISK_CACHE) {
      RecordDiskCacheInitTime(base::TimeTicks::Now() - init_start_time_);
    }
    result = disk_cache::BackendResult::Make(std::move(created_cache_));
  } else {
    LOG(ERROR) << "Unable to create cache";
    result = disk_cache::BackendResult::MakeError(
        static_cast<net::Error>(net_error));
    created_cache_.reset();
  }
  std::move(callback_).Run(std::move(result));
  delete this;
}

// If the initialization of the cache fails, and |reset_handling| isn't set to
// kNeverReset, we will discard the whole cache and create a new one.
void CacheCreator::OnEncryptionInitComplete(net::Error result) {
  if (result == net::OK) {
    disk_cache::BackendFileOperationsFactory*
        encrypted_file_operations_factory =
            cache_encryption_delegate_->GetEncryptionFileOperationsFactory(
                file_operations_factory_);
    if (encrypted_file_operations_factory) {
      file_operations_factory_ = encrypted_file_operations_factory;
      Run();
    } else {
      // TODO: crbug.com/478916662 - introduce a new error code or log to UMA
      // here.
      DoCallback(net::ERR_FAILED);
    }
  } else {
    DoCallback(result);
  }
}

void CacheCreator::OnIOComplete(int result) {
  DCHECK_NE(result, net::ERR_IO_PENDING);
  if (result == net::OK ||
      reset_handling_ == disk_cache::ResetHandling::kNeverReset || retry_) {
    return DoCallback(result);
  }

  // We are supposed to try again, so delete the object and all files and do so.
  retry_ = true;
  created_cache_.reset();

  if (!file_operations_) {
    if (file_operations_factory_) {
      file_operations_ = file_operations_factory_->Create(
          base::SequencedTaskRunner::GetCurrentDefault());
    } else {
      file_operations_ = std::make_unique<disk_cache::TrivialFileOperations>();
    }
  }
  file_operations_->CleanupDirectory(
      path_, base::BindOnce(&CacheCreator::OnCacheCleanupComplete,
                            base::Unretained(this), result));
}

void CacheCreator::OnCacheCleanupComplete(int original_result,
                                          bool cleanup_result) {
  if (!cleanup_result) {
    // Cleaning up the cache directory fails, so this operation should be
    // considered failed.
    DCHECK_NE(original_result, net::OK);
    DCHECK_NE(original_result, net::ERR_IO_PENDING);
    DoCallback(original_result);
    return;
  }

  // The worker thread may be deleting files, but the original folder
  // is not there anymore... let's create a new set of files.
  Run();
}

class TrivialFileEnumerator final : public FileEnumerator {
 public:
  using FileEnumerationEntry =
      disk_cache::BackendFileOperations::FileEnumerationEntry;

  explicit TrivialFileEnumerator(const base::FilePath& path)
      : enumerator_(path) {}
  ~TrivialFileEnumerator() override = default;

  std::optional<FileEnumerationEntry> Next() override {
    return enumerator_.Next();
  }
  bool HasError() const override { return enumerator_.HasError(); }

 private:
  disk_cache::SimpleFileEnumerator enumerator_;
};

class UnboundTrivialFileOperations
    : public disk_cache::UnboundBackendFileOperations {
 public:
  std::unique_ptr<disk_cache::BackendFileOperations> Bind(
      scoped_refptr<base::SequencedTaskRunner> task_runner) override {
    return std::make_unique<disk_cache::TrivialFileOperations>();
  }
};

}  // namespace

namespace disk_cache {

BackendResult::BackendResult() = default;
BackendResult::~BackendResult() = default;
BackendResult::BackendResult(BackendResult&&) = default;
BackendResult& BackendResult::operator=(BackendResult&&) = default;

// static
BackendResult BackendResult::MakeError(net::Error error_in) {
  DCHECK_NE(error_in, net::OK);
  BackendResult result;
  result.net_error = error_in;
  return result;
}

// static
BackendResult BackendResult::Make(std::unique_ptr<Backend> backend_in) {
  DCHECK(backend_in);
  BackendResult result;
  result.net_error = net::OK;
  result.backend = std::move(backend_in);
  return result;
}

BackendResult CreateCacheBackendImpl(
    net::CacheType type,
    net::BackendType backend_type,
    scoped_refptr<BackendFileOperationsFactory> file_operations,
    const base::FilePath& path,
    int64_t max_bytes,
    ResetHandling reset_handling,
#if BUILDFLAG(IS_ANDROID)
    ApplicationStatusListenerGetter app_status_listener_getter,
#endif
    net::NetLog* net_log,
    net::CacheEncryptionDelegate* cache_encryption_delegate,
    base::OnceClosure post_cleanup_callback,
    BackendResultCallback callback) {
  DCHECK(!callback.is_null());

  if (type == net::MEMORY_CACHE) {
    std::unique_ptr<MemBackendImpl> mem_backend_impl =
        disk_cache::MemBackendImpl::CreateBackend(max_bytes, net_log);
    if (mem_backend_impl) {
      mem_backend_impl->SetPostCleanupCallback(
          std::move(post_cleanup_callback));
      return BackendResult::Make(std::move(mem_backend_impl));
    } else {
      if (!post_cleanup_callback.is_null()) {
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, std::move(post_cleanup_callback));
      }
      return BackendResult::MakeError(net::ERR_FAILED);
    }
  }

  bool had_post_cleanup_callback = !post_cleanup_callback.is_null();
  // As document in disk_cache.h, `had_post_cleanup_callback` is not supported
  // for net::DISK_CACHE. Note: When kEnableBackendCleanupTrackerOnHttpCache is
  // enabled, we can support it jutt by removing this CHECK().
  CHECK(!((type == net::DISK_CACHE) && had_post_cleanup_callback));

  CacheCreator* creator =
      new CacheCreator(path, reset_handling, max_bytes, type, backend_type,
                       std::move(file_operations),
#if BUILDFLAG(IS_ANDROID)
                       std::move(app_status_listener_getter),
#endif
                       net_log, std::move(cache_encryption_delegate),
                       std::move(post_cleanup_callback), std::move(callback));
  if (type == net::DISK_CACHE &&
      !base::FeatureList::IsEnabled(
          net::features::kEnableBackendCleanupTrackerOnHttpCache)) {
    creator->InitEncryptionAndRun();
  } else {
    creator->TryCreateCleanupTrackerAndRun();
  }
  return BackendResult::MakeError(net::ERR_IO_PENDING);
}

BackendResult CreateCacheBackend(
    net::CacheType type,
    net::BackendType backend_type,
    scoped_refptr<BackendFileOperationsFactory> file_operations,
    const base::FilePath& path,
    int64_t max_bytes,
    ResetHandling reset_handling,
    net::NetLog* net_log,
    net::CacheEncryptionDelegate* cache_encryption_delegate,
    BackendResultCallback callback) {
  return CreateCacheBackendImpl(type, backend_type, std::move(file_operations),
                                path, max_bytes, reset_handling,
#if BUILDFLAG(IS_ANDROID)
                                ApplicationStatusListenerGetter(),
#endif
                                net_log, std::move(cache_encryption_delegate),
                                base::OnceClosure(), std::move(callback));
}

#if BUILDFLAG(IS_ANDROID)
NET_EXPORT BackendResult
CreateCacheBackend(net::CacheType type,
                   net::BackendType backend_type,
                   scoped_refptr<BackendFileOperationsFactory> file_operations,
                   const base::FilePath& path,
                   int64_t max_bytes,
                   ResetHandling reset_handling,
                   net::NetLog* net_log,
                   net::CacheEncryptionDelegate* cache_encryption_delegate,
                   BackendResultCallback callback,
                   ApplicationStatusListenerGetter app_status_listener_getter) {
  return CreateCacheBackendImpl(type, backend_type, std::move(file_operations),
                                path, max_bytes, reset_handling,
                                std::move(app_status_listener_getter), net_log,
                                cache_encryption_delegate,
                                base::OnceClosure(), std::move(callback));
}
#endif

BackendResult CreateCacheBackend(
    net::CacheType type,
    net::BackendType backend_type,
    scoped_refptr<BackendFileOperationsFactory> file_operations,
    const base::FilePath& path,
    int64_t max_bytes,
    ResetHandling reset_handling,
    net::NetLog* net_log,
    net::CacheEncryptionDelegate* cache_encryption_delegate,
    base::OnceClosure post_cleanup_callback,
    BackendResultCallback callback) {
  return CreateCacheBackendImpl(type, backend_type, std::move(file_operations),
                                path, max_bytes, reset_handling,
#if BUILDFLAG(IS_ANDROID)
                                ApplicationStatusListenerGetter(),
#endif
                                net_log, cache_encryption_delegate,
                                std::move(post_cleanup_callback),
                                std::move(callback));
}

void FlushCacheThreadForTesting() {
  // For simple backend.
  base::ThreadPoolInstance::Get()->FlushForTesting();

  // Block backend.
  BackendImpl::FlushForTesting();
}

void FlushCacheThreadAsynchronouslyForTesting(base::OnceClosure callback) {
  auto repeating_callback = base::BarrierClosure(2, std::move(callback));

  // For simple backend.
  base::ThreadPoolInstance::Get()->FlushAsyncForTesting(  // IN-TEST
      base::BindPostTaskToCurrentDefault(repeating_callback));

  // Block backend.
  BackendImpl::FlushAsynchronouslyForTesting(repeating_callback);
}

void WaitForBackendCleanupForTesting(const base::FilePath& path,  // IN-TEST
                                     base::OnceClosure callback) {
  auto [callback_1, callback_2] = base::SplitOnceCallback(std::move(callback));
  // TryCreate will return an instance only if there is no active tracker. In
  // this case, `callback_1` will not be run and no extra waiting is necessary,
  // so post `callback_2` to the current sequence to run soon. Otherwise, if
  // `TryCreate` returns null, `callback_1` has been added to its list of
  // callbacks to run when cleanup completes.
  if (BackendCleanupTracker::TryCreate(path, std::move(callback_1))) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(callback_2));
  }
}

int64_t Backend::CalculateSizeOfEntriesBetween(
    base::Time initial_time,
    base::Time end_time,
    Int64CompletionOnceCallback callback) {
  return net::ERR_NOT_IMPLEMENTED;
}

uint8_t Backend::GetEntryInMemoryData(const std::string& key) {
  return 0;
}

void Backend::OnBrowserIdle() {}

void Entry::SetEntryInMemoryData(uint8_t data) {}

EntryResult::EntryResult() = default;
EntryResult::~EntryResult() = default;

EntryResult::EntryResult(EntryResult&& other) {
  net_error_ = other.net_error_;
  entry_ = std::move(other.entry_);
  opened_ = other.opened_;

  other.net_error_ = net::ERR_FAILED;
  other.opened_ = false;
}

EntryResult& EntryResult::operator=(EntryResult&& other) {
  net_error_ = other.net_error_;
  entry_ = std::move(other.entry_);
  opened_ = other.opened_;

  other.net_error_ = net::ERR_FAILED;
  other.opened_ = false;
  return *this;
}

// static
EntryResult EntryResult::MakeOpened(Entry* new_entry) {
  DCHECK(new_entry);

  EntryResult result;
  result.net_error_ = net::OK;
  result.entry_.reset(new_entry);
  result.opened_ = true;
  return result;
}

// static
EntryResult EntryResult::MakeCreated(Entry* new_entry) {
  DCHECK(new_entry);

  EntryResult result;
  result.net_error_ = net::OK;
  result.entry_.reset(new_entry);
  result.opened_ = false;
  return result;
}

// static
EntryResult EntryResult::MakeError(net::Error status) {
  DCHECK_NE(status, net::OK);

  EntryResult result;
  result.net_error_ = status;
  return result;
}

Entry* EntryResult::ReleaseEntry() {
  Entry* ret = entry_.release();
  net_error_ = net::ERR_FAILED;
  opened_ = false;
  return ret;
}

TrivialFileOperations::TrivialFileOperations() {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

TrivialFileOperations::~TrivialFileOperations() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

bool TrivialFileOperations::CreateDirectory(const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  // This is needed for some unittests.
  if (path.empty()) {
    return false;
  }

  DCHECK(path.IsAbsolute());

  bool result = base::CreateDirectory(path);
  return result;
}

bool TrivialFileOperations::PathExists(const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  // This is needed for some unittests.
  if (path.empty()) {
    return false;
  }

  DCHECK(path.IsAbsolute());

  bool result = base::PathExists(path);
  return result;
}

bool TrivialFileOperations::DirectoryExists(const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(path.IsAbsolute());
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  bool result = base::DirectoryExists(path);
  return result;
}

std::unique_ptr<CacheFile> TrivialFileOperations::OpenFile(
    const base::FilePath& path,
    uint32_t flags) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(path.IsAbsolute());
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  base::File file(path, flags);
  return std::make_unique<BasicCacheFile>(std::move(file));
}

bool TrivialFileOperations::DeleteFile(const base::FilePath& path,
                                       DeleteFileMode mode) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(path.IsAbsolute());
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  bool result = false;
  switch (mode) {
    case DeleteFileMode::kDefault:
      result = base::DeleteFile(path);
      break;
    case DeleteFileMode::kEnsureImmediateAvailability:
      result = disk_cache::simple_util::SimpleCacheDeleteFile(path);
      break;
  }
  return result;
}

bool TrivialFileOperations::ReplaceFile(const base::FilePath& from_path,
                                        const base::FilePath& to_path,
                                        base::File::Error* error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(from_path.IsAbsolute());
  DCHECK(to_path.IsAbsolute());
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  return base::ReplaceFile(from_path, to_path, error);
}

std::optional<base::File::Info> TrivialFileOperations::GetFileInfo(
    const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(path.IsAbsolute());
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  base::File::Info file_info;
  if (!base::GetFileInfo(path, &file_info)) {
    return std::nullopt;
  }
  return file_info;
}

std::unique_ptr<FileEnumerator> TrivialFileOperations::EnumerateFiles(
    const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(path.IsAbsolute());
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif
  return std::make_unique<TrivialFileEnumerator>(path);
}

void TrivialFileOperations::CleanupDirectory(
    const base::FilePath& path,
    base::OnceCallback<void(bool)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // This is needed for some unittests.
  if (path.empty()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), false));
    return;
  }

  DCHECK(path.IsAbsolute());
#if DCHECK_IS_ON()
  DCHECK(bound_);
#endif

  disk_cache::CleanupDirectory(path, std::move(callback));
}

std::unique_ptr<UnboundBackendFileOperations> TrivialFileOperations::Unbind() {
#if DCHECK_IS_ON()
  DCHECK(bound_);
  bound_ = false;
#endif
  return std::make_unique<UnboundTrivialFileOperations>();
}

bool TrivialFileOperations::IsEncrypted() const {
  return false;
}

TrivialFileOperationsFactory::TrivialFileOperationsFactory() = default;
TrivialFileOperationsFactory::~TrivialFileOperationsFactory() = default;

std::unique_ptr<BackendFileOperations> TrivialFileOperationsFactory::Create(
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  return std::make_unique<TrivialFileOperations>();
}

std::unique_ptr<UnboundBackendFileOperations>
TrivialFileOperationsFactory::CreateUnbound() {
  return std::make_unique<UnboundTrivialFileOperations>();
}

}  // namespace disk_cache
