2020-12-23 07:02:02 +01:00
|
|
|
--[[
|
|
|
|
Implements a simple in-memory cache. The cache has no upper size limit, and
|
|
|
|
may cause out-of-memory errors. When this happens, the OS will kill the kore
|
|
|
|
worker process, and the kore parent process will restart with a fresh, empty
|
|
|
|
cache
|
|
|
|
]]
|
|
|
|
|
2020-12-20 09:16:23 +01:00
|
|
|
local sql = require("lsqlite3")
|
|
|
|
|
|
|
|
local queries = require("queries")
|
|
|
|
local util = require("util")
|
|
|
|
|
|
|
|
local ret = {}
|
|
|
|
|
2021-01-17 06:10:16 +01:00
|
|
|
local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache
|
2020-12-20 09:16:23 +01:00
|
|
|
|
|
|
|
local oldconfigure = configure
|
|
|
|
function configure(...)
|
2020-12-21 05:22:22 +01:00
|
|
|
local cache = util.sqlassert(sql.open_memory())
|
2021-07-28 02:39:04 +02:00
|
|
|
ret.cache = cache -- Expose db for testing
|
2020-12-20 09:16:23 +01:00
|
|
|
--A cache table to store rendered pages that do not need to be
|
|
|
|
--rerendered. In theory this could OOM the program eventually and start
|
|
|
|
--swapping to disk. TODO: fixme
|
|
|
|
assert(cache:exec([[
|
|
|
|
CREATE TABLE IF NOT EXISTS cache (
|
|
|
|
path TEXT PRIMARY KEY,
|
|
|
|
data BLOB,
|
|
|
|
updated INTEGER,
|
|
|
|
dirty INTEGER
|
|
|
|
);
|
|
|
|
]]))
|
2020-12-21 05:22:22 +01:00
|
|
|
stmnt_cache = assert(cache:prepare([[
|
2020-12-20 09:16:23 +01:00
|
|
|
SELECT data
|
|
|
|
FROM cache
|
|
|
|
WHERE
|
|
|
|
path = :path AND
|
|
|
|
((dirty = 0) OR (strftime('%s','now') - updated) < 20)
|
|
|
|
;
|
2020-12-21 05:22:22 +01:00
|
|
|
]]))
|
|
|
|
stmnt_insert_cache = assert(cache:prepare([[
|
2020-12-20 09:16:23 +01:00
|
|
|
INSERT OR REPLACE INTO cache (
|
|
|
|
path, data, updated, dirty
|
|
|
|
) VALUES (
|
|
|
|
:path, :data, strftime('%s','now'), 0
|
|
|
|
);
|
2020-12-21 05:22:22 +01:00
|
|
|
]]))
|
|
|
|
stmnt_dirty_cache = assert(cache:prepare([[
|
2020-12-20 09:16:23 +01:00
|
|
|
UPDATE OR IGNORE cache
|
|
|
|
SET dirty = 1
|
|
|
|
WHERE path = :path;
|
2020-12-21 05:22:22 +01:00
|
|
|
]]))
|
2020-12-20 09:16:23 +01:00
|
|
|
return oldconfigure(...)
|
|
|
|
end
|
|
|
|
|
|
|
|
--Render a page, with cacheing. If you need to dirty a cache, call dirty_cache()
|
|
|
|
function ret.render(pagename,callback)
|
|
|
|
stmnt_cache:bind_names{path=pagename}
|
|
|
|
local err = util.do_sql(stmnt_cache)
|
|
|
|
if err == sql.DONE then
|
|
|
|
stmnt_cache:reset()
|
|
|
|
--page is not cached
|
|
|
|
elseif err == sql.ROW then
|
2021-01-17 06:10:16 +01:00
|
|
|
local data = stmnt_cache:get_values()
|
2020-12-20 09:16:23 +01:00
|
|
|
stmnt_cache:reset()
|
|
|
|
return data[1]
|
|
|
|
else --sql.ERROR or sql.MISUSE
|
|
|
|
error("Failed to check cache for page " .. pagename)
|
|
|
|
end
|
|
|
|
--We didn't have the paged cached, render it
|
|
|
|
local text = callback()
|
|
|
|
--And save the data back into the cache
|
|
|
|
stmnt_insert_cache:bind_names{
|
|
|
|
path=pagename,
|
|
|
|
data=text,
|
|
|
|
}
|
|
|
|
err = util.do_sql(stmnt_insert_cache)
|
|
|
|
if err == sql.ERROR or err == sql.MISUSE then
|
|
|
|
error("Failed to update cache for page " .. pagename)
|
|
|
|
end
|
|
|
|
stmnt_insert_cache:reset()
|
|
|
|
return text
|
|
|
|
end
|
|
|
|
|
|
|
|
function ret.dirty(url)
|
|
|
|
stmnt_dirty_cache:bind_names{
|
|
|
|
path = url
|
|
|
|
}
|
2021-01-17 06:10:16 +01:00
|
|
|
util.do_sql(stmnt_dirty_cache)
|
2020-12-20 09:16:23 +01:00
|
|
|
stmnt_dirty_cache:reset()
|
|
|
|
end
|
|
|
|
|
|
|
|
function ret.close()
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
return ret
|