aboutsummaryrefslogtreecommitdiff
path: root/init.lua
blob: 02fee0d6bf95ac23a2f6e796d20debc1f66f2728 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
-- Copyright (c) 2024 Florian Fischer. All rights reserved.
--
-- vis-lockfiles is free software: you can redistribute it and/or modify it under
-- the terms of the GNU General Public License as published by the Free Software
-- Foundation, either version 3 of the License, or (at your option) any later
-- version.
--
-- vis-lockfiles is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- vis-lockfiles found in the LICENSE file.
-- If not, see <https://www.gnu.org/licenses/>.
local M = {}
M.directory = '${XDG_CACHE_HOME:-${HOME}/.local/share}/vis/locks'

-- Helper to execute a command and capture its output
local function capture_cmd(cmd)
  local p = assert(io.popen(cmd, 'r'))
  local s = assert(p:read('*a'))
  local success, _, status = p:close()
  if not success then
    local err = cmd .. ' failed with exit code: ' .. status
    vis:info('vis-lockfiles: ' .. err)
  end
  return s
end

local function lock_file_path(path)
  local filename = path:gsub('/', '%%')
  assert(os.execute('mkdir -p "' .. M.directory .. '"'))
  local lfile_path = capture_cmd('echo "' .. M.directory .. '/' .. filename .. '.lock"')
  return lfile_path:sub(1, #lfile_path - 1) -- trim newline from path
end

-- determine vis' pid
local vis_pid
do
  local vis_proc_file = io.open('/proc/self/stat', 'r')
  if vis_proc_file then
    vis_pid = vis_proc_file:read('*n')
    vis_proc_file:close()

  else -- fallback if /proc/self/stat is not available
    local out = capture_cmd('sh -c "echo $PPID"')
    vis_pid = tonumber(out)
  end
end
assert(vis_pid)

-- get a user confirmation
-- return true if user selected yes, false otherwise
local function confirm(path)
  local choices = 'no\nyes'
  local prompt = path .. ' exists. Edit anyway:'
  local cmd = 'printf "' .. choices .. '" | vis-menu -b -p \'' .. prompt .. '\''

  local choice = nil
  -- local status, output = vis:pipe(nil, {start = 0, finish = 0}, cmd)
  local menu = assert(io.popen(cmd))
  local output = menu:read('*a')
  local _, _, status = menu:close()
  if status == 0 then
    -- trim newline from selection
    if output:sub(-1) == '\n' then
      choice = output:sub(1, -2)
    else
      choice = output
    end
  end
  vis:redraw()
  return choice == 'yes'
end

local created_locks = {}
local function create_lock_file(lfile_path)
  local already_locked = os.execute('test -e ' .. '"' .. lfile_path .. '"')
  if already_locked then
    return false
  end

  -- create the lock file with our pid
  local cmd = 'echo ' .. vis_pid .. ' > "' .. lfile_path .. '"'
  assert(os.execute(cmd))

  -- detect a possible race
  local lock_file = io.open(lfile_path)
  assert(lock_file)
  local lock_pid = lock_file:read('*n')
  lock_file:close()
  local locked = tonumber(lock_pid) == vis_pid

  -- remember we created the lock file
  if locked then
    created_locks[lfile_path] = true
  end

  return locked
end

local function delete_lock_file(lfile_path)
  os.execute('rm ' .. '"' .. lfile_path .. '"')
  created_locks[lfile_path] = nil
end

vis.events.subscribe(vis.events.FILE_OPEN, function(file)
  local lfile_path = lock_file_path(file.path)
  if not create_lock_file(lfile_path) and not confirm(lfile_path) then
    os.exit(1)
  end
end)

vis.events.subscribe(vis.events.FILE_CLOSE, function(file)
  local lfile_path = lock_file_path(file.path)
  if created_locks[lfile_path] then
    delete_lock_file(lfile_path)
  end
end)

return M