Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/corrupt database #24

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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