nirish

config files for niri window manager
Log | Files | Refs | README | LICENSE

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