3#include <boost/asio/use_awaitable.hpp>
11namespace asio = boost::asio;
15using FileClock = std::filesystem::file_time_type::clock;
26class FileStore final :
public Haio::Cdn::CacheStore {
28 FileStore(std::filesystem::path root, std::chrono::seconds ttl,
size_t maxUsage)
29 : root_(std::move(root)), ttl_(ttl), maxUsage_(maxUsage) {
31 std::filesystem::create_directories(root_, ec);
35 asio::awaitable<std::optional<Haio::Cdn::CacheEntry>> get(
const std::string& key)
override {
36 const auto path = root_ / key;
39 const auto written = std::filesystem::last_write_time(path, ec);
40 if (ec)
co_return std::nullopt;
42 if (FileClock::now() - written >= ttl_) {
44 co_return std::nullopt;
47 std::ifstream in(path, std::ios::binary);
48 if (!in)
co_return std::nullopt;
50 const std::vector<uint8_t> raw((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
55 co_return std::nullopt;
59 std::filesystem::last_write_time(path, FileClock::now(), ec);
63 asio::awaitable<void> put(
const std::string& key,
const Haio::Cdn::CacheEntry& entry)
override {
65 if (raw.size() > maxUsage_)
co_return;
67 const auto path = root_ / key;
68 const auto pending = root_ / (key +
".tmp");
71 const auto replacing = std::filesystem::file_size(path, ec);
72 const auto previous = ec ?
size_t{0} :
static_cast<size_t>(replacing);
75 std::ofstream out(pending, std::ios::binary | std::ios::trunc);
77 out.write(
reinterpret_cast<const char*
>(raw.data()),
static_cast<std::streamsize
>(raw.size()));
80 std::filesystem::remove(pending, ec);
85 std::filesystem::rename(pending, path, ec);
87 std::filesystem::remove(pending, ec);
91 held_ = held_ - previous + raw.size();
92 if (held_ > maxUsage_) sweep();
98 size_t measure()
const {
101 for (
const auto& item : std::filesystem::directory_iterator(root_, ec)) {
102 std::error_code each;
103 if (
const auto size = item.file_size(each); !each) total +=
static_cast<size_t>(size);
108 void forget(
const std::filesystem::path& path) {
110 const auto size = std::filesystem::file_size(path, ec);
111 if (!ec && !std::filesystem::remove(path, ec))
return;
112 if (!ec) held_ -= std::min(held_,
static_cast<size_t>(size));
122 std::filesystem::path file;
123 std::filesystem::file_time_type written;
127 std::vector<Aged> kept;
129 const auto now = FileClock::now();
131 for (
const auto& item : std::filesystem::directory_iterator(root_, ec)) {
132 std::error_code each;
133 const auto written = item.last_write_time(each);
135 const auto size = item.file_size(each);
139 if (item.path().extension() ==
".tmp" || now - written >= ttl_) {
140 std::error_code drop;
141 std::filesystem::remove(item.path(), drop);
144 kept.push_back(Aged{item.path(), written,
static_cast<size_t>(size)});
148 for (
const auto& item : kept) total += item.size;
150 if (total > maxUsage_) {
151 std::ranges::sort(kept, {}, &Aged::written);
152 for (
const auto& item : kept) {
153 if (total <= maxUsage_)
break;
154 std::error_code drop;
155 if (std::filesystem::remove(item.file, drop)) total -= item.size;
161 std::filesystem::path root_;
162 std::chrono::seconds ttl_;
171std::unique_ptr<CacheStore>
makeFileStore(std::filesystem::path root, std::chrono::seconds ttl,
size_t maxUsage) {
172 return std::make_unique<FileStore>(std::move(root), ttl, maxUsage);
std::optional< CacheEntry > decodeEntry(std::span< const uint8_t > raw)
std::vector< uint8_t > encodeEntry(const CacheEntry &entry)
std::unique_ptr< CacheStore > makeFileStore(std::filesystem::path root, std::chrono::seconds ttl, size_t maxUsage)