mirror of
https://github.com/koreader/koreader
synced 2024-11-04 12:00:25 +00:00
2420557085
Regression since #9529 Fix #9565
121 lines
4.3 KiB
Lua
121 lines
4.3 KiB
Lua
--[[
|
|
"Global" LRU cache used by Document & friends.
|
|
--]]
|
|
|
|
local Cache = require("cache")
|
|
local CanvasContext = require("document/canvascontext")
|
|
local DataStorage = require("datastorage")
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
local logger = require("logger")
|
|
local md5 = require("ffi/sha2").md5
|
|
local util = require("util")
|
|
|
|
local DHINTCOUNT = G_defaults:readSetting("DHINTCOUNT")
|
|
|
|
local function calcCacheMemSize()
|
|
local min = G_defaults:readSetting("DGLOBAL_CACHE_SIZE_MINIMUM")
|
|
local max = G_defaults:readSetting("DGLOBAL_CACHE_SIZE_MAXIMUM")
|
|
local memfree, _ = util.calcFreeMem() or 0, 0
|
|
local calc = memfree * G_defaults:readSetting("DGLOBAL_CACHE_FREE_PROPORTION")
|
|
return math.min(max, math.max(min, calc))
|
|
end
|
|
local doccache_size = calcCacheMemSize()
|
|
|
|
local function computeCacheSize()
|
|
local mb_size = doccache_size / 1024 / 1024
|
|
|
|
-- If we end up with a not entirely ridiculous cache size, use that...
|
|
if mb_size >= 8 then
|
|
logger.dbg(string.format("Allocating a %dMB budget for the global document cache", mb_size))
|
|
return doccache_size
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function computeCacheSlots()
|
|
local mb_size = doccache_size / 1024 / 1024
|
|
|
|
--- ...otherwise, effectively disable the cache by making it single slot...
|
|
if mb_size < 8 then
|
|
logger.dbg(string.format("Setting up a minimal single slot global document cache"))
|
|
return 1
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local DocCache = Cache:new{
|
|
slots = computeCacheSlots(),
|
|
size = computeCacheSize(),
|
|
-- Average item size is a screen's worth of bitmap, mixed with a few much smaller tables (pgdim, pglinks, etc.), hence the / 3
|
|
avg_itemsize = math.floor(CanvasContext:getWidth() * CanvasContext:getHeight() * (CanvasContext.is_color_rendering_enabled and 4 or 1) / 3),
|
|
-- Rely on CacheItem's eviction callback to free resources *immediately* on eviction.
|
|
enable_eviction_cb = true,
|
|
disk_cache = true,
|
|
cache_path = DataStorage:getDataDir() .. "/cache/",
|
|
}
|
|
|
|
function DocCache:serialize(doc_path)
|
|
if not self.disk_cache then
|
|
return
|
|
end
|
|
|
|
-- Calculate the current disk cache size
|
|
local cached_size = 0
|
|
local sorted_caches = {}
|
|
for _, file in pairs(self.cached) do
|
|
table.insert(sorted_caches, {file=file, time=lfs.attributes(file, "access")})
|
|
cached_size = cached_size + (lfs.attributes(file, "size") or 0)
|
|
end
|
|
table.sort(sorted_caches, function(v1, v2) return v1.time > v2.time end)
|
|
|
|
-- Rewind a bit in order to serialize the currently *displayed* page for the current document,
|
|
-- as the actual MRU item would be the most recently *hinted* page, which wouldn't be helpful ;).
|
|
if doc_path then
|
|
local mru_key
|
|
local mru_found = 0
|
|
for key, item in self.cache:pairs() do
|
|
-- Only dump items that actually request persistence and match the current document.
|
|
if item.persistent and item.dump and item.doc_path == doc_path then
|
|
mru_key = key
|
|
mru_found = mru_found + 1
|
|
if mru_found >= (1 + DHINTCOUNT) then
|
|
-- We found the right item, i.e., the *displayed* page
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if mru_key then
|
|
local cache_full_path = self.cache_path .. md5(mru_key)
|
|
local cache_file_exists = lfs.attributes(cache_full_path)
|
|
|
|
if not cache_file_exists then
|
|
logger.dbg("Dumping cache item", mru_key)
|
|
local cache_item = self.cache:get(mru_key)
|
|
local cache_size = cache_item:dump(cache_full_path)
|
|
if cache_size then
|
|
cached_size = cached_size + cache_size
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Allocate the same amount of storage to the disk cache than the memory cache
|
|
while cached_size > self.size do
|
|
-- discard the least recently used cache
|
|
local discarded = table.remove(sorted_caches)
|
|
if discarded then
|
|
cached_size = cached_size - lfs.attributes(discarded.file, "size")
|
|
os.remove(discarded.file)
|
|
else
|
|
logger.warn("Cache accounting is broken")
|
|
break
|
|
end
|
|
end
|
|
-- We may have updated the disk cache's content, so refresh its state
|
|
self:refreshSnapshot()
|
|
end
|
|
|
|
return DocCache
|