Configure synchronization of files and directories through a plugin.
| 1 | local rsync = require("syncmap.rsync") |
| 2 | local utils = require("syncmap.utils") |
| 3 | local log = require("syncmap.log") |
| 4 | local simple_cmd = require("syncmap.simple_cmd") |
| 5 | local M = { |
| 6 | opts = require("syncmap.default"), |
| 7 | } |
| 8 | |
| 9 | M.dir = vim.fn.expand("~/.local/share/nvim/syncmap/") |
| 10 | |
| 11 | ---Make sure the directory exists |
| 12 | if vim.fn.isdirectory(M.dir) == 0 then |
| 13 | vim.fn.mkdir(M.dir, "p") |
| 14 | end |
| 15 | |
| 16 | M.state_file = vim.fn.expand("~/.local/share/nvim/syncmap/state.json") |
| 17 | |
| 18 | -- Create file if it doesn't exist |
| 19 | local |
| 20 | if vim.fn.filereadable(M.state_file) == 0 then |
| 21 | local fd = io.open(M.state_file, "w") |
| 22 | if fd then |
| 23 | fd:write("{}") -- empty JSON object |
| 24 | fd:close() |
| 25 | else |
| 26 | utils.log("Failed to create state file", vim.log.levels.ERROR) |
| 27 | end |
| 28 | end |
| 29 | end |
| 30 | create_state_file() |
| 31 | |
| 32 | ---@alias RunningPid string|integer The process id for a SyncmapWatchItem |
| 33 | ---@alias RunningTag string A key based on src:dst for a SyncmapWatchItem |
| 34 | ---@alias RunningSrcs table<RsyncPath, RunningPid> |
| 35 | |
| 36 | ---A dictionairy keyed on the src for a match located in ~/.local/share/nvim/syncmap/state.json |
| 37 | ---@type RunningSrcs |
| 38 | M.active = {} |
| 39 | |
| 40 | ---Load the existing config if possible |
| 41 | local |
| 42 | local content = vim.fn.readfile(M.state_file) |
| 43 | local joined = table.concat(content, "\n") |
| 44 | local ok, parsed = pcall(vim.json.decode, joined) |
| 45 | if ok and type(parsed) == "table" then |
| 46 | M.active = parsed |
| 47 | else |
| 48 | utils.log("Failed to parse state file", vim.log.levels.ERROR) |
| 49 | end |
| 50 | end |
| 51 | load_state() |
| 52 | |
| 53 | ---Removes stale states, calls reverse_rsync on new folders, and starts watch on dead pids |
| 54 | |
| 55 | local opts = M.opts |
| 56 | ---@type table<RunningTag, SyncmapWatchItem> |
| 57 | local lookup = {} |
| 58 | for _, m in ipairs(opts.map) do |
| 59 | local tag = vim.fn.expand(m[1]) .. ":" .. vim.fn.expand(m[2]) |
| 60 | lookup[tag] = m |
| 61 | end |
| 62 | |
| 63 | -- Iterate active and sync what exists |
| 64 | for tag, pid in pairs(M.active) do |
| 65 | if not lookup[tag] then |
| 66 | log.info(string.format("Removed from the config.\n%s\nRemoving the process %s.", tag, pid)) |
| 67 | M.active[tag] = nil |
| 68 | simple_cmd.kill(pid) |
| 69 | elseif not simple_cmd.exists(pid) then |
| 70 | local m = lookup[tag] |
| 71 | local r = utils.row_to_rsync_params(m) |
| 72 | local cmd_str |
| 73 | M.active[tag], cmd_str = rsync.spawn_watcher(r) |
| 74 | log.info( |
| 75 | string.format( |
| 76 | "In active table but the process %s is not running.\n%s\n%s\nStarting a new process at %s.", |
| 77 | pid, |
| 78 | cmd_str, |
| 79 | tag, |
| 80 | M.active[tag] |
| 81 | ) |
| 82 | ) |
| 83 | end |
| 84 | end |
| 85 | |
| 86 | ---Iterate config and add new items |
| 87 | for _, m in ipairs(opts.map) do |
| 88 | local tag = vim.fn.expand(m[1]) .. ":" .. vim.fn.expand(m[2]) |
| 89 | if not M.active[tag] then |
| 90 | local r = utils.row_to_rsync_params(m) |
| 91 | local cmd_str |
| 92 | M.active[tag], cmd_str = rsync.spawn_watcher(r) |
| 93 | log.info( |
| 94 | string.format( |
| 95 | "%s\nIn the config but not in the active table.\n%s\nNew process is %s.", |
| 96 | tag, |
| 97 | cmd_str, |
| 98 | M.active[tag] |
| 99 | ) |
| 100 | ) |
| 101 | end |
| 102 | end |
| 103 | M.save() |
| 104 | end |
| 105 | |
| 106 | |
| 107 | local ok, encoded = pcall(vim.json.encode, M.active) |
| 108 | if not ok then |
| 109 | log.error("Failed to encode state") |
| 110 | return |
| 111 | end |
| 112 | |
| 113 | local fd = io.open(M.state_file, "w") |
| 114 | if not fd then |
| 115 | log.error("Failed to open state file for writing") |
| 116 | return |
| 117 | end |
| 118 | |
| 119 | fd:write(encoded) |
| 120 | fd:close() |
| 121 | end |
| 122 | |
| 123 | ---Kills all the active inotifywatch servers |
| 124 | |
| 125 | local s = "" |
| 126 | for tag, pid in pairs(M.active) do |
| 127 | s = string.format("%s\n%d: %s", s, pid, tag) |
| 128 | simple_cmd.kill(pid) |
| 129 | end |
| 130 | log.info(string.format("Killed the following watch items\n%s", s)) |
| 131 | M.active = {} |
| 132 | end |
| 133 | |
| 134 | ---Clears the saved information by deleting the state file |
| 135 | |
| 136 | local ok, err = os.remove(M.state_file) |
| 137 | if not ok then |
| 138 | log.error("Failed to delete state file: " .. err) |
| 139 | else |
| 140 | log.info("State file deleted") |
| 141 | end |
| 142 | end |
| 143 | |
| 144 | ---Restarts Syncmap |
| 145 | |
| 146 | M.killall() |
| 147 | M.sync() |
| 148 | end |
| 149 | |
| 150 | ---List active |
| 151 | |
| 152 | vim.print(M.active) |
| 153 | end |
| 154 | |
| 155 | ---Shows the current options of Syncmap |
| 156 | |
| 157 | vim.print(M.opts) |
| 158 | end |
| 159 | |
| 160 | return M |