diff --git a/lua/bookmarks/backup/init.lua b/lua/bookmarks/backup/init.lua new file mode 100644 index 0000000..92a0897 --- /dev/null +++ b/lua/bookmarks/backup/init.lua @@ -0,0 +1,100 @@ +local M = {} +local fn = vim.fn +local uv = vim.loop + +---Get backup directory path from config or fallback +---@param backup_dir? string +---@return string +local function get_backup_dir(backup_dir) + if not backup_dir then + return fn.stdpath("data") .. "/bookmarks.backup" + end + return backup_dir +end +M.get_backup_dir = get_backup_dir + +---Create backup directory if it doesn't exist +---@param dir string +local function ensure_backup_dir(dir) + if fn.isdirectory(dir) == 0 then + local ok = fn.mkdir(dir, "p") + if ok == 0 then + error(string.format("Failed to create backup directory: %s", dir)) + end + end +end + +---@param src string Source file path +---@param dst string Destination file path +local function copy_file(src, dst) + local source = uv.fs_open(src, "r", 438) + if not source then + error(string.format("Failed to open source file: %s", src)) + return + end + + local stat = uv.fs_fstat(source) + if not stat then + uv.fs_close(source) + error(string.format("Failed to stat source file: %s", src)) + return + end + + local data = uv.fs_read(source, stat.size, 0) + uv.fs_close(source) + + local dest = uv.fs_open(dst, "w", 438) + if not dest then + error(string.format("Failed to open destination file: %s", dst)) + return + end + + uv.fs_write(dest, data) + uv.fs_close(dest) +end + +---Create backup of database file +---@param db_path string Path to database file +---@param backup_dir string Path to backup directory +---@return string|nil # Backup file path or nil if backup failed +local function create_backup(db_path, backup_dir) + -- Check if source file exists + if fn.filereadable(db_path) == 0 then + return + end + + local timestamp = os.date("%Y%m%d_%H%M%S") + local backup_path = string.format("%s/bookmarks_%s.sqlite.db", backup_dir, timestamp) + copy_file(db_path, backup_path) + return backup_path +end + +---Setup backup with 5-minute delay +---@param config table +---@param db_path string +function M.setup(config, db_path) + if not config.backup.enabled then + return + end + + local backup_dir = get_backup_dir(config.backup.dir) + ensure_backup_dir(backup_dir) + + -- Create a one-shot timer for delayed backup + local timer = uv.new_timer() + timer:start( + config.backup.delay * 60 * 1000, + -- 5, -- For testing + 0, + vim.schedule_wrap(function() + local backup_path = create_backup(db_path, backup_dir) + vim.notify("Bookmarks backup created: " .. backup_path, vim.log.levels.INFO, { title = "Bookmarks.nvim" }) + timer:close() + end) + ) + + -- Store timer reference to prevent garbage collection + M._timer = timer +end + +return M diff --git a/lua/bookmarks/config.lua b/lua/bookmarks/config.lua index 1cec6bb..073005e 100644 --- a/lua/bookmarks/config.lua +++ b/lua/bookmarks/config.lua @@ -8,6 +8,15 @@ local default_config = { -- 2. Create `bookmarks.sqlite.db` inside this directory ---@type string? db_dir = nil, -- if nil, fallback to default `stdpath("data")` + backup = { + enabled = true, + -- Directory to store backup files + -- Default: vim.fn.stdpath("data").."/bookmarks.backup" + -- You can set a custom directory + ---@type string? + dir = nil, + delay = 5, -- Delay in minutes before nvim opened, no back will be created if nvim earlier than the actually backup time + }, -- Navigation configurations navigation = { @@ -127,6 +136,7 @@ local setup = function(user_config) require("bookmarks.domain.repo").setup(get_db_path(cfg.db_dir)) require("bookmarks.sign").setup(cfg.signs) require("bookmarks.auto-cmd").setup() + require("bookmarks.backup").setup(cfg, get_db_path(cfg.db_dir)) end return { diff --git a/lua/bookmarks/info/init.lua b/lua/bookmarks/info/init.lua index b2d7dfe..9f6083d 100644 --- a/lua/bookmarks/info/init.lua +++ b/lua/bookmarks/info/init.lua @@ -1,6 +1,7 @@ local Repo = require("bookmarks.domain.repo") local Window = require("bookmarks.utils.window") local Location = require("bookmarks.domain.location") +local Backup = require("bookmarks.backup") local M = {} @@ -44,6 +45,7 @@ end ---Get info of bookmarks function M.get_info() + local config = vim.g.bookmarks_config or {} local sections = { "# Bookmarks Info\n", } @@ -60,11 +62,12 @@ function M.get_info() table.insert(sections, string.format("- Total Bookmarks: `%d`\n", #all_bookmarks)) table.insert(sections, string.format("- Total Lists: `%d`\n", #all_lists)) table.insert(sections, string.format("- Database Location: `%s`\n", Repo._DB.uri)) + local backup_dir = Backup.get_backup_dir(config.backup.dir) + table.insert(sections, string.format("- Backup Location: `%s`\n", backup_dir)) -- Configuration section table.insert(sections, "## Configuration\n") table.insert(sections, "```lua") - local config = vim.g.bookmarks_config or {} for key, value in pairs(config) do if type(value) ~= "function" then table.insert(sections, string.format("%s = %s", key, vim.inspect(value)))