1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
|
// 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"
#include "components/persistent_cache/backend_type.h"
#include "components/persistent_cache/pending_backend.h"
#include "components/persistent_cache/persistent_cache.h"
#if !BUILDFLAG(IS_FUCHSIA)
#include "components/persistent_cache/sqlite/backend_storage_delegate.h"
#endif
namespace persistent_cache {
namespace {
std::unique_ptr<BackendStorage::Delegate> MakeDelegateOfType(
BackendType backend_type) {
#if BUILDFLAG(IS_FUCHSIA)
return nullptr;
#else
switch (backend_type) {
case BackendType::kSqlite:
return std::make_unique<sqlite::BackendStorageDelegate>();
}
#endif
}
// 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(BackendType backend_type,
base::FilePath directory)
: BackendStorage(MakeDelegateOfType(backend_type), 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::optional<PendingBackend> BackendStorage::MakePendingBackend(
const base::FilePath& base_name,
bool single_connection,
bool journal_mode_wal) {
return is_valid_
? delegate_->MakePendingBackend(
directory_, base_name, single_connection, journal_mode_wal)
: std::nullopt;
}
std::unique_ptr<Backend> BackendStorage::MakeBackend(
const base::FilePath& base_name,
bool single_connection,
bool journal_mode_wal) {
return is_valid_ ? delegate_->MakeBackend(directory_, base_name,
single_connection, journal_mode_wal)
: nullptr;
}
std::optional<PendingBackend> BackendStorage::ShareReadOnlyConnection(
const base::FilePath& base_name,
const PersistentCache& cache) {
return is_valid_ ? delegate_->ShareReadOnlyConnection(directory_, base_name,
cache.backend())
: std::nullopt;
}
std::optional<PendingBackend> BackendStorage::ShareReadWriteConnection(
const base::FilePath& base_name,
const PersistentCache& cache) {
return is_valid_ ? delegate_->ShareReadWriteConnection(directory_, base_name,
cache.backend())
: std::nullopt;
}
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
|