telescope-frecency.nvim/lua/frecency/file_lock.lua
JINNOUCHI Yasushi 58c0089414
fix!: register realpath for consistency (#240)
Now it uses realpath for registering and validating DB. This means, if you have entries that has filenames differing only for case, it can deal with them as they exist.

Before this, it has miscalculated scores for such cases. For example, in case you have `/path/to/foo.lua` and `/path/to/Foo.lua`, it registers entries for each file. Now it detects accurate filename for the specified one, and removes it in validation.

* test: separate logic for utils

* fix!: register realpath for consistency

* refactor: convert fs module from class

* refactor: move db initialization phase to start()

* fix: run database:start() truly asynchronously

* fix: call each functions with async wrapping

* refactor: add types for args in command

* fix: run register() synchronously

Because vim.api.nvim_* cannot be used in asynchronous functions.

* docs: add note for calling setup() twice

* fix: run non-fast logic on next tick
2024-08-15 17:40:03 +09:00

108 lines
3.0 KiB
Lua

local log = require "frecency.log"
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
---@class FrecencyFileLock
---@field base string
---@field config FrecencyFileLockRawConfig
---@field lock string
---@field target string
local FileLock = {}
---@class FrecencyFileLockConfig
---@field retry? integer default: 5
---@field unlink_retry? integer default: 5
---@field interval? integer default: 500
---@class FrecencyFileLockRawConfig
---@field retry integer default: 5
---@field unlink_retry integer default: 5
---@field interval integer default: 500
---@param target string
---@param file_lock_config? FrecencyFileLockConfig
---@return FrecencyFileLock
FileLock.new = function(target, file_lock_config)
log.debug(("file_lock new(): %s"):format(target))
local config = vim.tbl_extend("force", { retry = 5, unlink_retry = 5, interval = 500 }, file_lock_config or {})
return setmetatable({ config = config, lock = target .. ".lock", target = target }, { __index = FileLock })
end
---@async
---@return string? err
function FileLock:get()
local count = 0
local unlink_count = 0
local err, fd
while true do
count = count + 1
local dir = Path.new(self.lock):parent()
if not dir:exists() then
-- TODO: make this call be async
log.debug(("file_lock get(): mkdir parent: %s"):format(dir.filename))
---@diagnostic disable-next-line: undefined-field
dir:mkdir { parents = true }
end
err, fd = async.uv.fs_open(self.lock, "wx", tonumber("600", 8))
if not err then
break
end
async.util.sleep(self.config.interval)
if count >= self.config.retry then
log.debug(("file_lock get(): retry count reached. try to delete the lock file: %d"):format(count))
err = async.uv.fs_unlink(self.lock)
if err then
log.debug("file_lock get() failed: " .. err)
unlink_count = unlink_count + 1
if unlink_count >= self.config.unlink_retry then
log.error("file_lock get(): failed to unlink the lock file: " .. err)
return "failed to get lock"
end
end
end
log.debug(("file_lock get() retry: %d"):format(count))
end
err = async.uv.fs_close(fd)
if err then
log.debug("file_lock get() failed: " .. err)
return err
end
end
---@async
---@return string? err
function FileLock:release()
local err = async.uv.fs_stat(self.lock)
if err then
log.debug("file_lock release() not found: " .. err)
return "lock not found"
end
err = async.uv.fs_unlink(self.lock)
if err then
log.debug("file_lock release() unlink failed: " .. err)
return err
end
end
---@async
---@generic T
---@param f fun(target: string): T
---@return string? err
---@return T
function FileLock:with(f)
local err = self:get()
if err then
return err, nil
end
local ok, result_or_err = pcall(f, self.target)
err = self:release()
if err then
return err, nil
elseif ok then
return nil, result_or_err
end
return result_or_err, nil
end
return FileLock