blob: ca0428330f9470b1221553e4768523232bddfc87 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/persistent_cache/backend_storage.h"
#include <algorithm>
#include <functional>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/numerics/clamped_math.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/persistent_cache/backend.h"
#if !BUILDFLAG(IS_FUCHSIA)
#include "components/persistent_cache/sqlite/backend_storage_delegate.h"
#endif
namespace persistent_cache {
namespace {
// Deletes the contents of `directory` without deleting `directory` itself.
void DeleteDirectoryContents(const base::FilePath& directory) {
base::FileEnumerator(directory, /*recursive=*/false,
base::FileEnumerator::NAMES_ONLY)
.ForEach([](const base::FilePath& path) {
base::DeletePathRecursively(path);
});
}
} // namespace
BackendStorage::BackendStorage(base::FilePath directory)
: BackendStorage(
#if BUILDFLAG(IS_FUCHSIA)
nullptr,
#else
std::make_unique<sqlite::BackendStorageDelegate>(),
#endif
std::move(directory)) {
}
BackendStorage::BackendStorage(std::unique_ptr<Delegate> delegate,
base::FilePath directory)
: delegate_(std::move(delegate)), directory_(std::move(directory)) {
CHECK(!directory_.empty());
is_valid_ = delegate_ && base::CreateDirectory(directory_);
}
BackendStorage::~BackendStorage() = default;
std::unique_ptr<Backend> BackendStorage::MakeBackend(
const base::FilePath& base_name) {
return is_valid_ ? delegate_->MakeBackend(directory_, base_name) : nullptr;
}
void BackendStorage::DeleteAllFiles() {
if (is_valid_) {
// All files are opened with FLAG_WIN_SHARE_DELETE, so it is possible to
// delete them even if any are still open. The parent directory will not
// be deleted in this case, but that's okay.
// TODO(https://crbug.com/377475540): On Windows, a file cannot be marked
// for deletion while it is mapped into a process's address space. If WAL
// mode is used, we will need to investigate if the wal-index ("-shm") file
// can always be opened with `FLAG_DELETE_ON_CLOSE` so that it is
// unconditionally deleted when the DB is closed.
// https://sqlite.org/walformat.html#shm indicates that this should be safe.
DeleteDirectoryContents(directory_);
}
}
void BackendStorage::DeleteFiles(const base::FilePath& base_name) {
if (is_valid_) {
delegate_->DeleteFiles(directory_, base_name);
}
}
BackendStorage::FootprintReductionResult
BackendStorage::BringDownTotalFootprintOfFiles(int64_t target_footprint) {
if (!is_valid_) {
return {0, 0};
}
// Measure the size of the directory while collecting the basenames and
// last modified times of each of the backend's files.
int64_t total_footprint = 0;
std::vector<std::pair<base::FilePath, base::Time>> base_names;
base::FileEnumerator(directory_, /*recursive=*/false,
base::FileEnumerator::FILES)
.ForEach([&total_footprint, &base_names,
&delegate = *delegate_](const base::FilePath& file_path) {
base::File::Info info;
// Ignore errors -- info.last_modified will be zero, so this will look
// like the oldest file; first to be deleted.
base::GetFileInfo(file_path, &info);
// Only target files managed by the backend for deletion.
if (auto base_name = delegate.GetBaseName(file_path);
!base_name.empty()) {
base_names.emplace_back(std::move(base_name), info.last_modified);
}
// All files count towards measured footprint.
total_footprint = base::ClampAdd(total_footprint, info.size);
});
// Nothing to do.
if (total_footprint <= target_footprint) {
return FootprintReductionResult{.current_footprint = total_footprint};
}
// Sort the collected base names oldest to newest.
std::ranges::sort(base_names, std::ranges::less(),
&std::pair<base::FilePath, base::Time>::second);
// Delete files until reaching the desired target.
const int64_t size_of_necessary_deletes = total_footprint - target_footprint;
int64_t deleted_size = 0;
for (const auto& [base_name, last_modified_time] : base_names) {
deleted_size = base::ClampAdd(
deleted_size, delegate_->DeleteFiles(directory_, base_name));
if (deleted_size >= size_of_necessary_deletes) {
break;
}
}
return FootprintReductionResult{
.current_footprint = total_footprint - deleted_size,
.number_of_bytes_deleted = deleted_size};
}
} // namespace persistent_cache