util/disk_cache.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include "chrono_batch_render/util/disk_cache.h" | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <atomic> | ||
| 6 | #include <chrono> | ||
| 7 | #include <cstdint> | ||
| 8 | #include <fstream> | ||
| 9 | #include <functional> | ||
| 10 | #include <iterator> | ||
| 11 | #include <system_error> | ||
| 12 | #include <thread> | ||
| 13 | #include <vector> | ||
| 14 | |||
| 15 | #include <fmt/format.h> | ||
| 16 | |||
| 17 | #include "chrono_batch_render/util/environment.h" | ||
| 18 | #include "chrono_batch_render/util/log.h" | ||
| 19 | #include "chrono_batch_render/util/text.h" | ||
| 20 | #include "chrono_batch_render/util/version.h" | ||
| 21 | |||
| 22 | namespace chrono::batch_render { | ||
| 23 | namespace { | ||
| 24 | |||
| 25 | 2651 | std::string Preamble(std::string_view key) { | |
| 26 | 2651 | return fmt::format("chrono_batch_render cache 1\n{}\n", key); | |
| 27 | } | ||
| 28 | |||
| 29 | 192 | std::string UniqueSuffix() { | |
| 30 | 192 | static std::atomic<uint64_t> counter{0}; | |
| 31 | 192 | const auto now = std::chrono::steady_clock::now().time_since_epoch().count(); | |
| 32 | 192 | return fmt::format(".{:x}.{:x}.{:x}.tmp", | |
| 33 | 576 | std::hash<std::thread::id>{}(std::this_thread::get_id()), uint64_t(now), | |
| 34 | 192 | counter++); | |
| 35 | } | ||
| 36 | |||
| 37 | 192 | bool WriteTemporary(const std::filesystem::path& temporary, std::string_view key, | |
| 38 | std::string_view bytes) { | ||
| 39 | 192 | std::ofstream file(temporary, std::ios::binary | std::ios::trunc); | |
| 40 | 384 | file << Preamble(key) << bytes; | |
| 41 | 192 | return bool(file.flush()); | |
| 42 | 192 | } | |
| 43 | |||
| 44 | } | ||
| 45 | |||
| 46 | 5320 | std::filesystem::path DiskCache::Path(std::string_view key) const { | |
| 47 | 15960 | return m_directory / (Digest(key) + ".bin"); | |
| 48 | } | ||
| 49 | |||
| 50 | 2671 | std::optional<std::string> DiskCache::Load(std::string_view key) const { | |
| 51 | 5342 | std::ifstream file(Path(key), std::ios::binary); | |
| 52 | 2671 | if (!file) { | |
| 53 | 212 | return std::nullopt; | |
| 54 | } | ||
| 55 | |||
| 56 | 4918 | std::string bytes{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()}; | |
| 57 | 2459 | const std::string preamble = Preamble(key); | |
| 58 | 2459 | if (!bytes.starts_with(preamble)) { | |
| 59 | 2 | return std::nullopt; | |
| 60 | } | ||
| 61 | 2457 | std::error_code error; | |
| 62 | 2457 | std::filesystem::last_write_time(Path(key), std::filesystem::file_time_type::clock::now(), | |
| 63 | error); | ||
| 64 | 4914 | return bytes.substr(preamble.size()); | |
| 65 | 2671 | } | |
| 66 | |||
| 67 | 192 | void DiskCache::Store(std::string_view key, std::string_view bytes) const { | |
| 68 | 192 | const std::filesystem::path path = Path(key); | |
| 69 | 576 | const std::filesystem::path temporary = path.string() + UniqueSuffix(); | |
| 70 | 192 | std::error_code error; | |
| 71 | 192 | std::filesystem::create_directories(m_directory, error); | |
| 72 | 192 | if (!WriteTemporary(temporary, key, bytes)) { | |
| 73 | 1 | Log(LogLevel::Debug, "can't write {} to the disk cache", temporary.string()); | |
| 74 | 1 | std::filesystem::remove(temporary, error); | |
| 75 | 1 | return; | |
| 76 | } | ||
| 77 | |||
| 78 | 191 | std::filesystem::rename(temporary, path, error); | |
| 79 | 191 | if (error) { | |
| 80 | 2 | Log(LogLevel::Debug, "can't add {} to the disk cache: {}", path.string(), error.message()); | |
| 81 | 1 | std::filesystem::remove(temporary, error); | |
| 82 | } | ||
| 83 | 384 | } | |
| 84 | |||
| 85 | 30 | std::filesystem::path UserCacheDirectory() { | |
| 86 | 30 | if (const std::string chosen = Environment("CHRONO_BATCH_RENDER_CACHE_DIR"); !chosen.empty()) { | |
| 87 | 1 | return chosen; | |
| 88 | 1 | } | |
| 89 | #if defined(_WIN32) | ||
| 90 | if (const std::string local = Environment("LOCALAPPDATA"); !local.empty()) { | ||
| 91 | return std::filesystem::path(local) / "chrono-batch-render" / "cache"; | ||
| 92 | } | ||
| 93 | #elif defined(__APPLE__) | ||
| 94 | if (const std::string home = Environment("HOME"); !home.empty()) { | ||
| 95 | return std::filesystem::path(home) / "Library" / "Caches" / "chrono-batch-render"; | ||
| 96 | } | ||
| 97 | #else | ||
| 98 | 29 | if (const std::string cache = Environment("XDG_CACHE_HOME"); !cache.empty()) { | |
| 99 | 3 | return std::filesystem::path(cache) / "chrono-batch-render"; | |
| 100 | 1 | } | |
| 101 | 28 | if (const std::string home = Environment("HOME"); !home.empty()) { | |
| 102 | 135 | return std::filesystem::path(home) / ".cache" / "chrono-batch-render"; | |
| 103 | 27 | } | |
| 104 | #endif | ||
| 105 | 1 | std::error_code error; | |
| 106 | 3 | return std::filesystem::temp_directory_path(error) / "chrono-batch-render-cache"; | |
| 107 | } | ||
| 108 | |||
| 109 | 22 | std::filesystem::path KernelCacheDirectory(const std::filesystem::path& root) { | |
| 110 | 22 | const Version version = GetBuildInfo().version; | |
| 111 | 88 | return root / "kernels" / fmt::format("{}.{}.{}", version.major, version.minor, version.patch); | |
| 112 | } | ||
| 113 | |||
| 114 | 13 | void TrimKernelCache(const std::filesystem::path& root, uint64_t limit) { | |
| 115 | 13 | namespace fs = std::filesystem; | |
| 116 | 13 | std::error_code error; | |
| 117 | |||
| 118 | 82894 | struct Entry { | |
| 119 | fs::path path; | ||
| 120 | fs::file_time_type used; | ||
| 121 | uint64_t bytes; | ||
| 122 | }; | ||
| 123 | |||
| 124 | 13 | std::vector<Entry> entries; | |
| 125 | 13 | uint64_t total = 0; | |
| 126 | 12731 | for (const fs::directory_entry& entry : | |
| 127 | 12781 | fs::recursive_directory_iterator(root / "kernels", error)) { | |
| 128 | 12705 | if (!entry.is_regular_file(error)) { | |
| 129 | 94 | continue; | |
| 130 | } | ||
| 131 | 12611 | const uint64_t bytes = entry.file_size(error); | |
| 132 | 12611 | entries.push_back({entry.path(), entry.last_write_time(error), bytes}); | |
| 133 | 12611 | total += bytes; | |
| 134 | 13 | } | |
| 135 | 13 | if (total <= limit) { | |
| 136 | 12 | return; | |
| 137 | } | ||
| 138 | 1 | std::ranges::sort(entries, {}, &Entry::used); | |
| 139 | 3 | for (const Entry& entry : entries) { | |
| 140 | 3 | if (total <= limit) { | |
| 141 | break; | ||
| 142 | } | ||
| 143 | 2 | if (fs::remove(entry.path, error)) { | |
| 144 | 2 | total -= entry.bytes; | |
| 145 | } | ||
| 146 | } | ||
| 147 | 12624 | } | |
| 148 | |||
| 149 | } | ||
| 150 |