Skip to content

Commit

Permalink
Fix/corrupt database (#24)
Browse files Browse the repository at this point in the history
* Update RoamSave, RoamUpdate, and RoamDatabaseReset to support 'sync' argument
* Update roam.setup()/roam.setup.call() to return a promise of the OrgRoam plugin
* Refactor tests to use utils.init_plugin
* Update tests to use init_before_test and cleanup_after_test throughout
* Fix some tests to be more consistent
* Update roam.db:load() to have force be either boolean or "scan", where scan will only reload all files via orgmode and not force modification reloading
* Rename RoamDatabaseReset -> RoamReset
  • Loading branch information
chipsenkbeil authored Apr 23, 2024
1 parent 0034e37 commit 356d696
Show file tree
Hide file tree
Showing 17 changed files with 251 additions and 275 deletions.
49 changes: 43 additions & 6 deletions lua/org-roam/database.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ end

---Loads the database from disk and re-parses files.
---Returns a promise that receives a database reference and collection of files.
---@param opts? {force?:boolean}
---
---If `force` is "scan", the directory will be searched again for files
---and they will be reloaded. Modification of database records will not be
---forced.
---
---If `force` is true, the directory will be searched again for files,
---they will be reloaded, and modifications of database records will be forced.
---@param opts? {force?:boolean|"scan"}
---@return OrgPromise<{database:org-roam.core.Database, files:OrgFiles}>
function M:load(opts)
opts = opts or {}
Expand Down Expand Up @@ -131,8 +138,11 @@ function M:load_file(opts)
end

---Saves the database to disk.
---
---Returns a promise of a boolean indicating if the database was actually
---written to disk, or if it was cached.
---@param opts? {force?:boolean}
---@return OrgPromise<nil>
---@return OrgPromise<boolean>
function M:save(opts)
opts = opts or {}
log.fmt_debug("saving database (force=%s)", opts.force or false)
Expand All @@ -142,14 +152,14 @@ function M:save(opts)

return self:__get_loader():database():next(function(db)
-- If our last save was recent enough, do not actually save
if self.__last_save >= db:changed_tick() then
if not opts.force and self.__last_save >= db:changed_tick() then
profiler:stop(rec_id)
log.fmt_debug("saving database took %s (nothing to save)",
profiler:time_taken_as_string({ recording = rec_id }))
return Promise.resolve(nil)
return Promise.resolve(false)
end

-- Refresh our data to make sure it is fresh
-- Refresh our data (no rescan or force) to make sure it is fresh
return self:load():next(function()
return Promise.new(function(resolve, reject)
db:write_to_disk(self.__database_path, function(err)
Expand All @@ -168,7 +178,7 @@ function M:save(opts)
profiler:time_taken_as_string({ recording = rec_id }))

self.__last_save = db:changed_tick()
resolve(nil)
resolve(true)
end)
end)
end)
Expand Down Expand Up @@ -216,6 +226,33 @@ function M:files_sync(opts)
return self:__get_loader():files_sync(opts)
end

---Inserts a node into the database.
---
---Returns a promise of the id tied to the node in the database.
---@param node org-roam.core.file.Node
---@param opts? {overwrite?:boolean}
---@return OrgPromise<string>
function M:insert(node, opts)
opts = opts or {}

---@diagnostic disable-next-line:missing-return-value
return self:__get_loader():database():next(function(db)
return db:insert(node, {
id = node.id,
overwrite = opts.overwrite,
})
end)
end

---Retrieves a node from the database by its id.
---@param node org-roam.core.file.Node
---@param opts? {overwrite?:boolean, timeout?:integer}
---@return org-roam.core.file.Node|nil
function M:insert_sync(node, opts)
opts = opts or {}
return self:insert(node, opts):wait(opts.timeout)
end

---Retrieves a node from the database by its id.
---@param id org-roam.core.database.Id
---@return OrgPromise<org-roam.core.file.Node|nil>
Expand Down
19 changes: 13 additions & 6 deletions lua/org-roam/database/loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -192,16 +192,23 @@ end
---Loads all files into the database. If files have not been modified
---from current database record, they will be ignored.
---
---@param opts? {force?:boolean}
---If `force` is "scan", the directory will be searched again for files
---and they will be reloaded. Modification of database records will not be
---forced.
---
---If `force` is true, the directory will be searched again for files,
---they will be reloaded, and modifications of database records will be forced.
---@param opts? {force?:boolean|"scan"}
---@return OrgPromise<{database:org-roam.core.Database, files:OrgFiles}>
function M:load(opts)
opts = opts or {}
local force = opts.force or false
local force_modify = opts.force == true
local force_scan = opts.force == "scan" or opts.force == true

-- Reload all org-roam files
return Promise.all({
self:database(),
self:files({ force = force }),
self:files({ force = force_scan }),
}):next(function(results)
---@type org-roam.core.Database, OrgFiles
local db, files = results[1], results[2]
Expand Down Expand Up @@ -242,7 +249,7 @@ function M:load(opts)
if file then
log.fmt_debug("inserting into database: %s", file.filename)
return insert_new_file_into_database(db, file, {
force = force or file.metadata.changedtick ~= changedtick,
force = force_modify or file.metadata.changedtick ~= changedtick,
})
else
return 0
Expand All @@ -266,7 +273,7 @@ function M:load(opts)
if file then
log.fmt_debug("modifying in database: %s", file.filename)
return modify_file_in_database(db, file, {
force = force or file.metadata.changedtick ~= changedtick,
force = force_modify or file.metadata.changedtick ~= changedtick,
})
else
return 0
Expand Down Expand Up @@ -347,7 +354,7 @@ end
---Loads database (or retrieves from cache) asynchronously.
---@return OrgPromise<org-roam.core.Database>
function M:database()
return self.__db and Promise.resolve(self.__db) or Promise.new(function(resolve, reject)
return self.__db and Promise.resolve(self.__db) or Promise.new(function(resolve)
-- Load our database from disk if it is available
io.stat(self.path.database, function(unavailable)
if unavailable then
Expand Down
79 changes: 55 additions & 24 deletions lua/org-roam/setup.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,37 +99,57 @@ local function define_commands(roam)
local Profiler = require("org-roam.core.utils.profiler")

vim.api.nvim_create_user_command("RoamSave", function(opts)
log.fmt_debug("Saving database")
local force = opts.bang or false
local args = opts.args or ""
local sync = string.lower(vim.trim(args)) == "sync"
log.fmt_debug("Saving database (sync = %s)", sync)

-- Start profiling so we can report the time taken
local profiler = Profiler:new()
profiler:start()

roam.db:save():next(function(...)
local promise = roam.db:save({ force = force }):next(function(...)
local tt = profiler:stop():time_taken_as_string()
notify.info("Saved database [took " .. tt .. "]")
return ...
end):catch(notify.error)
end, { bang = true, desc = "Saves the roam database to disk" })

if sync then promise:wait() end
end, {
bang = true,
desc = "Saves the roam database to disk",
nargs = "?",
})

vim.api.nvim_create_user_command("RoamUpdate", function(opts)
local force = opts.bang or false
local args = opts.args or ""
local sync = string.lower(vim.trim(args)) == "sync"

-- Start profiling so we can report the time taken
local profiler = Profiler:new()
profiler:start()

log.fmt_debug("Updating database (force = %s)", force)
roam.db:load({ force = force }):next(function(...)
log.fmt_debug("Updating database (force = %s, sync = %s)", force, sync)
local promise = roam.db:load({ force = force }):next(function(...)
local tt = profiler:stop():time_taken_as_string()
notify.info("Updated database [took " .. tt .. "]")
return ...
end):catch(notify.error)
end, { bang = true, desc = "Updates the roam database" })

vim.api.nvim_create_user_command("RoamDatabaseReset", function()
log.debug("Resetting database")
roam.db:delete_disk_cache():next(function(success)
if sync then promise:wait() end
end, {
bang = true,
desc = "Updates the roam database",
nargs = "?",
})

vim.api.nvim_create_user_command("RoamReset", function(opts)
local args = opts.args or ""
local sync = string.lower(vim.trim(args)) == "sync"

log.fmt_debug("Resetting database (sync = %s)", sync)
local promise = roam.db:delete_disk_cache():next(function(success)
roam.db = roam.db:new({
db_path = roam.db:path(),
directory = roam.db:files_path(),
Expand All @@ -138,15 +158,18 @@ local function define_commands(roam)
-- Start profiling so we can report the time taken
local profiler = Profiler:new()
profiler:start()
roam.db:load():next(function(...)
return roam.db:load():next(function(...)
local tt = profiler:stop():time_taken_as_string()
notify.info("Loaded database [took " .. tt .. "]")
return ...
end):catch(notify.error)

return success
end)
end, { desc = "Completely wipes the roam database" })

if sync then promise:wait() end
end, {
desc = "Resets the roam database (wipe and rebuild)",
nargs = "?",
})

vim.api.nvim_create_user_command("RoamAddAlias", function(opts)
---@type string|nil
Expand Down Expand Up @@ -448,6 +471,7 @@ local function modify_orgmode_plugin(roam)
end

---@param roam OrgRoam
---@return OrgPromise<{database:org-roam.core.Database, files:OrgFiles}>
local function initialize_database(roam)
local Promise = require("orgmode.utils.promise")

Expand All @@ -457,8 +481,8 @@ local function initialize_database(roam)
directory = roam.config.directory,
})

-- Load the database asynchronously
roam.db:load():next(function()
-- Load the database asynchronously, forcing a full sweep of directory
return roam.db:load({ force = "scan" }):next(function()
-- If we are persisting to disk, do so now as the database may
-- have changed post-load
if roam.config.database.persist then
Expand All @@ -473,69 +497,76 @@ end
---@return org-roam.Setup
return function(roam)
---@class org-roam.Setup
---@operator call(org-roam.Config):nil
---@operator call(org-roam.Config):OrgPromise<OrgRoam>
local M = setmetatable({}, {
__call = function(this, config)
this.call(config)
return this.call(config)
end
})

---Calls the setup function to initialize the plugin.
---@param config org-roam.Config|nil
---@return OrgPromise<OrgRoam>
function M.call(config)
M.__merge_config(config or {})
M.__define_autocmds()
M.__define_commands()
M.__define_keybindings()
M.__modify_orgmode_plugin()
M.__initialize_database()
return M.__initialize_database():next(function()
return roam
end)
end

---@private
function M.__merge_config(config)
if not M.__merge_config_done then
merge_config(roam, config)
M.__merge_config_done = true
merge_config(roam, config)
end
end

---@private
function M.__define_autocmds()
if not M.__define_autocmds_done then
define_autocmds(roam)
M.__define_autocmds_done = true
define_autocmds(roam)
end
end

---@private
function M.__define_commands()
if not M.__define_commands_done then
define_commands(roam)
M.__define_commands_done = true
define_commands(roam)
end
end

---@private
function M.__define_keybindings()
if not M.__define_keybindings_done then
define_keybindings(roam)
M.__define_keybindings_done = true
define_keybindings(roam)
end
end

---@private
function M.__modify_orgmode_plugin()
if not M.__modify_orgmode_plugin_done then
modify_orgmode_plugin(roam)
M.__modify_orgmode_plugin_done = true
modify_orgmode_plugin(roam)
end
end

---@private
---@return OrgPromise<nil>
function M.__initialize_database()
if not M.__initialize_database_done then
initialize_database(roam)
M.__initialize_database_done = true
return initialize_database(roam):next(function() return nil end)
else
local Promise = require("orgmode.utils.promise")
return Promise.resolve(nil)
end
end

Expand Down
22 changes: 6 additions & 16 deletions spec/api_alias_spec.lua
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
describe("org-roam.api.alias", function()
local roam = require("org-roam")
local roam --[[ @type OrgRoam ]]
local utils = require("spec.utils")

---@type string
local test_org_file_path

before_each(function()
local dir = utils.make_temp_directory()
utils.init_before_test()

roam = utils.init_plugin({ setup = true })
test_org_file_path = utils.make_temp_filename({
dir = dir,
dir = roam.config.directory,
ext = "org",
})

roam.db = roam.db:new({
db_path = vim.fn.tempname() .. "-test-db",
directory = dir,
})

-- Patch `vim.cmd` so we can run tests here
utils.patch_vim_cmd()
end)

after_each(function()
-- Unpatch `vim.cmd` so we can have tests pass
utils.unpatch_vim_cmd()

-- Restore select in case we mocked it
utils.unmock_select()
utils.cleanup_after_test()
end)

it("should be able to add the first alias to the node under cursor", function()
Expand Down
Loading

0 comments on commit 356d696

Please sign in to comment.