#include "cache.hh" #include "sqlite.hh" #include "sync.hh" #include "store-api.hh" #include "users.hh" #include namespace nix::fetchers { static const char * schema = R"sql( create table if not exists Cache ( input text not null, info text not null, path text not null, immutable integer not null, timestamp integer not null, primary key (input) ); )sql"; struct CacheImpl : Cache { struct State { SQLite db; SQLiteStmt add, lookup; }; Sync _state; CacheImpl() { auto state(_state.lock()); auto dbPath = getCacheDir() + "/nix/fetcher-cache-v1.sqlite"; // It would be silly to fail fetcher operations if e.g. the user has no // XDG_CACHE_HOME and their HOME directory doesn't exist. // We'll warn the user if that happens, but fallback to an in-memory // backend for the SQLite database. try { createDirs(dirOf(dbPath)); } catch (SysError const & ex) { warn("ignoring error initializing Lix fetcher cache: %s", ex.what()); dbPath = ":memory:"; } state->db = SQLite(dbPath); state->db.isCache(); state->db.exec(schema); state->add.create(state->db, "insert or replace into Cache(input, info, path, immutable, timestamp) values (?, ?, ?, ?, ?)"); state->lookup.create(state->db, "select info, path, immutable, timestamp from Cache where input = ?"); } void add( ref store, const Attrs & inAttrs, const Attrs & infoAttrs, const StorePath & storePath, bool locked) override { _state.lock()->add.use() (attrsToJSON(inAttrs).dump()) (attrsToJSON(infoAttrs).dump()) (store->printStorePath(storePath)) (locked) (time(0)).exec(); } std::optional> lookup( ref store, const Attrs & inAttrs) override { if (auto res = lookupExpired(store, inAttrs)) { if (!res->expired) return std::make_pair(std::move(res->infoAttrs), std::move(res->storePath)); debug("ignoring expired cache entry '%s'", attrsToJSON(inAttrs).dump()); } return {}; } std::optional lookupExpired( ref store, const Attrs & inAttrs) override { auto state(_state.lock()); auto inAttrsJSON = attrsToJSON(inAttrs).dump(); auto stmt(state->lookup.use()(inAttrsJSON)); if (!stmt.next()) { debug("did not find cache entry for '%s'", inAttrsJSON); return {}; } auto infoJSON = stmt.getStr(0); auto storePath = store->parseStorePath(stmt.getStr(1)); auto locked = stmt.getInt(2) != 0; auto timestamp = stmt.getInt(3); store->addTempRoot(storePath); if (!store->isValidPath(storePath)) { // FIXME: we could try to substitute 'storePath'. debug("ignoring disappeared cache entry '%s'", inAttrsJSON); return {}; } debug("using cache entry '%s' -> '%s', '%s'", inAttrsJSON, infoJSON, store->printStorePath(storePath)); return Result { .expired = !locked && (settings.tarballTtl.get() == 0 || timestamp + settings.tarballTtl < time(0)), .infoAttrs = jsonToAttrs(nlohmann::json::parse(infoJSON)), .storePath = std::move(storePath) }; } }; ref getCache() { static auto cache = std::make_shared(); return ref(cache); } }