summaryrefslogtreecommitdiff
path: root/mpv/scripts/file-browser/modules/addons/cache.lua
diff options
context:
space:
mode:
Diffstat (limited to 'mpv/scripts/file-browser/modules/addons/cache.lua')
-rw-r--r--mpv/scripts/file-browser/modules/addons/cache.lua146
1 files changed, 146 insertions, 0 deletions
diff --git a/mpv/scripts/file-browser/modules/addons/cache.lua b/mpv/scripts/file-browser/modules/addons/cache.lua
new file mode 100644
index 0000000..b1011fd
--- /dev/null
+++ b/mpv/scripts/file-browser/modules/addons/cache.lua
@@ -0,0 +1,146 @@
+--[[
+ This file is an internal file-browser addon.
+ It should not be imported like a normal module.
+
+ Maintains a cache of the accessed directories to improve
+ parsing speed. Disabled by default.
+]]
+
+local mp = require 'mp'
+local msg = require 'mp.msg'
+local utils = require 'mp.utils'
+
+local fb = require 'file-browser'
+
+---@type ParserConfig
+local cacheParser = {
+ name = 'cache',
+ priority = 0,
+ api_version = '1.9',
+}
+
+---@class CacheEntry
+---@field list List
+---@field opts Opts?
+---@field timeout MPTimer
+
+---@type table<string,CacheEntry>
+local cache = {}
+
+---@type table<string,(async fun(list: List?, opts: Opts?))[]>
+local pending_parses = {}
+
+---@param directories? string[]
+local function clear_cache(directories)
+ if directories then
+ msg.debug('clearing cache for', #directories, 'directorie(s)')
+ for _, dir in ipairs(directories) do
+ if cache[dir] then
+ msg.trace('clearing cache for', dir)
+ cache[dir].timeout:kill()
+ cache[dir] = nil
+ end
+ end
+ else
+ msg.debug('clearing cache')
+ for _, entry in pairs(cache) do
+ entry.timeout:kill()
+ end
+ cache = {}
+ end
+end
+
+---@type string
+local prev_directory = ''
+
+function cacheParser:can_parse(directory, parse_state)
+ -- allows the cache to be forcibly used or bypassed with the
+ -- cache/use parse property.
+ if parse_state.properties.cache and parse_state.properties.cache.use ~= nil then
+ if parse_state.source == 'browser' then prev_directory = directory end
+ return parse_state.properties.cache.use
+ end
+
+ -- the script message is guaranteed to always bypass the cache
+ if parse_state.source == 'script-message' then return false end
+ if not fb.get_opt('cache') or directory == '' then return false end
+
+ -- clear the cache if reloading the current directory in the browser
+ -- this means that fb.rescan() should maintain expected behaviour
+ if parse_state.source == 'browser' then
+ if prev_directory == directory then clear_cache({directory}) end
+ prev_directory = directory
+ end
+
+ return true
+end
+
+---@async
+function cacheParser:parse(directory)
+ if cache[directory] then
+ msg.verbose('fetching', directory, 'contents from cache')
+ cache[directory].timeout:kill()
+ cache[directory].timeout:resume()
+ return cache[directory].list, cache[directory].opts
+ end
+
+ ---@type List?, Opts?
+ local list, opts
+
+ -- if another parse is already running on the same directory, then wait and use the same result
+ if not pending_parses[directory] then
+ pending_parses[directory] = {}
+ list, opts = self:defer(directory)
+ else
+ msg.debug('parse for', directory, 'already running - waiting for other parse to finish...')
+ table.insert(pending_parses[directory], fb.coroutine.callback(30))
+ list, opts = coroutine.yield()
+ end
+
+ local pending = pending_parses[directory]
+ -- need to clear the pending parses before resuming them or they will also attempt to resume the parses
+ pending_parses[directory] = nil
+ if pending and #pending > 0 then
+ msg.debug('resuming', #pending, 'pending parses for', directory)
+ for _, cb in ipairs(pending) do
+ cb(list, opts)
+ end
+ end
+
+ if not list then return end
+
+ -- pending will be truthy for the original parse and falsy for any parses that were pending
+ if pending then
+ msg.debug('storing', directory, 'contents in cache')
+ cache[directory] = {
+ list = list,
+ opts = opts,
+ timeout = mp.add_timeout(120, function() cache[directory] = nil end),
+ }
+ end
+
+ return list, opts
+end
+
+cacheParser.keybinds = {
+ {
+ key = 'Ctrl+Shift+r',
+ name = 'clear',
+ command = function() clear_cache() ; fb.rescan() end,
+ }
+}
+
+-- provide method of clearing the cache through script messages
+mp.register_script_message('cache/clear', function(dirs)
+ if not dirs then
+ return clear_cache()
+ end
+
+ ---@type string[]?
+ local directories = utils.parse_json(dirs)
+ if not directories then msg.error('unable to parse', dirs) end
+
+ clear_cache(directories)
+end)
+
+return cacheParser