cache.lua (4317B)
1 --[[ 2 This file is an internal file-browser addon. 3 It should not be imported like a normal module. 4 5 Maintains a cache of the accessed directories to improve 6 parsing speed. Disabled by default. 7 ]] 8 9 local mp = require 'mp' 10 local msg = require 'mp.msg' 11 local utils = require 'mp.utils' 12 13 local fb = require 'file-browser' 14 15 ---@type ParserConfig 16 local cacheParser = { 17 name = 'cache', 18 priority = 0, 19 api_version = '1.9', 20 } 21 22 ---@class CacheEntry 23 ---@field list List 24 ---@field opts Opts? 25 ---@field timeout MPTimer 26 27 ---@type table<string,CacheEntry> 28 local cache = {} 29 30 ---@type table<string,(async fun(list: List?, opts: Opts?))[]> 31 local pending_parses = {} 32 33 ---@param directories? string[] 34 local function clear_cache(directories) 35 if directories then 36 msg.debug('clearing cache for', #directories, 'directorie(s)') 37 for _, dir in ipairs(directories) do 38 if cache[dir] then 39 msg.trace('clearing cache for', dir) 40 cache[dir].timeout:kill() 41 cache[dir] = nil 42 end 43 end 44 else 45 msg.debug('clearing cache') 46 for _, entry in pairs(cache) do 47 entry.timeout:kill() 48 end 49 cache = {} 50 end 51 end 52 53 ---@type string 54 local prev_directory = '' 55 56 function cacheParser:can_parse(directory, parse_state) 57 -- allows the cache to be forcibly used or bypassed with the 58 -- cache/use parse property. 59 if parse_state.properties.cache and parse_state.properties.cache.use ~= nil then 60 if parse_state.source == 'browser' then prev_directory = directory end 61 return parse_state.properties.cache.use 62 end 63 64 -- the script message is guaranteed to always bypass the cache 65 if parse_state.source == 'script-message' then return false end 66 if not fb.get_opt('cache') or directory == '' then return false end 67 68 -- clear the cache if reloading the current directory in the browser 69 -- this means that fb.rescan() should maintain expected behaviour 70 if parse_state.source == 'browser' then 71 if prev_directory == directory then clear_cache({directory}) end 72 prev_directory = directory 73 end 74 75 return true 76 end 77 78 ---@async 79 function cacheParser:parse(directory) 80 if cache[directory] then 81 msg.verbose('fetching', directory, 'contents from cache') 82 cache[directory].timeout:kill() 83 cache[directory].timeout:resume() 84 return cache[directory].list, cache[directory].opts 85 end 86 87 ---@type List?, Opts? 88 local list, opts 89 90 -- if another parse is already running on the same directory, then wait and use the same result 91 if not pending_parses[directory] then 92 pending_parses[directory] = {} 93 list, opts = self:defer(directory) 94 else 95 msg.debug('parse for', directory, 'already running - waiting for other parse to finish...') 96 table.insert(pending_parses[directory], fb.coroutine.callback(30)) 97 list, opts = coroutine.yield() 98 end 99 100 local pending = pending_parses[directory] 101 -- need to clear the pending parses before resuming them or they will also attempt to resume the parses 102 pending_parses[directory] = nil 103 if pending and #pending > 0 then 104 msg.debug('resuming', #pending, 'pending parses for', directory) 105 for _, cb in ipairs(pending) do 106 cb(list, opts) 107 end 108 end 109 110 if not list then return end 111 112 -- pending will be truthy for the original parse and falsy for any parses that were pending 113 if pending then 114 msg.debug('storing', directory, 'contents in cache') 115 cache[directory] = { 116 list = list, 117 opts = opts, 118 timeout = mp.add_timeout(120, function() cache[directory] = nil end), 119 } 120 end 121 122 return list, opts 123 end 124 125 cacheParser.keybinds = { 126 { 127 key = 'Ctrl+Shift+r', 128 name = 'clear', 129 command = function() clear_cache() ; fb.rescan() end, 130 } 131 } 132 133 -- provide method of clearing the cache through script messages 134 mp.register_script_message('cache/clear', function(dirs) 135 if not dirs then 136 return clear_cache() 137 end 138 139 ---@type string[]? 140 local directories = utils.parse_json(dirs) 141 if not directories then msg.error('unable to parse', dirs) end 142 143 clear_cache(directories) 144 end) 145 146 return cacheParser