From f85612e0f025e32a9e6aa264b713c13a5585985c Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 6 Mar 2022 00:59:22 -0500 Subject: [PATCH] Fix Project Scanning (#746) Removed dmon, and replaced with logic that works across Linux, Mac, FreeBSD and Windows. Have tested on all platforms, and seems to work. Co-authored-by: Jan200101 --- README.md | 1 + data/core/commands/core.lua | 1 - data/core/dirwatch.lua | 220 ++++ data/core/init.lua | 536 +++------ data/plugins/treeview.lua | 5 +- lib/dmon/dmon.h | 1685 --------------------------- lib/dmon/dmon_extra.h | 165 --- lib/dmon/meson.build | 1 - licenses/licenses.md | 27 - meson.build | 6 +- resources/notes-dmon-integration.md | 54 - src/api/api.c | 13 +- src/api/api.h | 1 + src/api/dirmonitor.c | 203 ++++ src/api/system.c | 131 +-- src/dirmonitor.c | 59 - src/dirmonitor.h | 15 - src/main.c | 7 +- src/meson.build | 2 +- 19 files changed, 588 insertions(+), 2544 deletions(-) create mode 100644 data/core/dirwatch.lua delete mode 100644 lib/dmon/dmon.h delete mode 100644 lib/dmon/dmon_extra.h delete mode 100644 lib/dmon/meson.build delete mode 100644 resources/notes-dmon-integration.md create mode 100644 src/api/dirmonitor.c delete mode 100644 src/dirmonitor.c delete mode 100644 src/dirmonitor.h diff --git a/README.md b/README.md index b54c17e4..b5c31a05 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ See the [licenses] file for details on licenses used by the required dependencie [Get color themes]: https://github.com/lite-xl/lite-xl-colors [changelog]: https://github.com/lite-xl/lite-xl/blob/master/changelog.md [Lite XL plugins repository]: https://github.com/lite-xl/lite-xl-plugins +[plugins repository]: https://github.com/rxi/lite-plugins [colors repository]: https://github.com/lite-xl/lite-xl-colors [LICENSE]: LICENSE [licenses]: licenses/licenses.md diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index 524352fc..84ec9b91 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -173,7 +173,6 @@ command.add(nil, { end if abs_path == core.project_dir then return end core.confirm_close_docs(core.docs, function(dirpath) - core.close_current_project() core.open_folder_project(dirpath) end, abs_path) end, suggest_directory) diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua new file mode 100644 index 00000000..3848e5a2 --- /dev/null +++ b/data/core/dirwatch.lua @@ -0,0 +1,220 @@ +local common = require "core.common" +local config = require "core.config" +local dirwatch = {} + +function dirwatch:__index(idx) + local value = rawget(self, idx) + if value ~= nil then return value end + return dirwatch[idx] +end + +function dirwatch.new() + local t = { + scanned = {}, + watched = {}, + reverse_watched = {}, + monitor = dirmonitor.new(), + windows_watch_top = nil, + windows_watch_count = 0 + } + setmetatable(t, dirwatch) + return t +end + + +function dirwatch:scan(directory, bool) + if bool == false then return self:unwatch(directory) end + self.scanned[directory] = system.get_file_info(directory).modified +end + +-- Should be called on every directory in a subdirectory. +-- In windows, this is a no-op for anything underneath a top-level directory, +-- but code should be called anyway, so we can ensure that we have a proper +-- experience across all platforms. +function dirwatch:watch(directory, bool) + if bool == false then return self:unwatch(directory) end + if not self.watched[directory] and not self.scanned[directory] then + if PLATFORM == "Windows" then + if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then + -- Get the highest level of directory that is common to this directory, and the original. + local target = directory + while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do + target = common.dirname(target) + end + if target ~= self.windows_watch_top then + local value = self.monitor:watch(target) + if value and value < 0 then + return self:scan(directory) + end + self.windows_watch_top = target + self.windows_watch_count = self.windows_watch_count + 1 + end + end + self.watched[directory] = true + else + local value = self.monitor:watch(directory) + -- If for whatever reason, we can't watch this directory, revert back to scanning. + -- Don't bother trying to find out why, for now. + if value and value < 0 then + return self:scan(directory) + end + self.watched[directory] = value + self.reverse_watched[value] = directory + end + end +end + +function dirwatch:unwatch(directory) + if self.watched[directory] then + if PLATFORM ~= "Windows" then + self.monitor.unwatch(directory) + self.reverse_watched[self.watched[directory]] = nil + else + self.windows_watch_count = self.windows_watch_count - 1 + if self.windows_watch_count == 0 then + self.windows_watch_top = nil + self.monitor.unwatch(directory) + end + end + self.watched[directory] = nil + elseif self.scanned[directory] then + self.scanned[directory] = nil + end +end + +-- designed to be run inside a coroutine. +function dirwatch:check(change_callback, scan_time, wait_time) + self.monitor:check(function(id) + if PLATFORM == "Windows" then + change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id)) + elseif self.reverse_watched[id] then + change_callback(self.reverse_watched[id]) + end + end) + local start_time = system.get_time() + for directory, old_modified in pairs(self.scanned) do + if old_modified then + local new_modified = system.get_file_info(directory).modified + if old_modified < new_modified then + change_callback(directory) + self.scanned[directory] = new_modified + end + end + if system.get_time() - start_time > scan_time then + coroutine.yield(wait_time) + start_time = system.get_time() + end + end +end + + +local function strip_leading_path(filename) + return filename:sub(2) +end + +-- inspect config.ignore_files patterns and prepare ready to use entries. +local function compile_ignore_files() + local ipatterns = config.ignore_files + local compiled = {} + -- config.ignore_files could be a simple string... + if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end + for i, pattern in ipairs(ipatterns) do + -- we ignore malformed pattern that raise an error + if pcall(string.match, "a", pattern) then + table.insert(compiled, { + use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end + -- An '/' or '/$' at the end means we want to match a directory. + match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value + pattern = pattern -- get the actual pattern + }) + end + end + return compiled +end + + +local function fileinfo_pass_filter(info, ignore_compiled) + if info.size >= config.file_size_limit * 1e6 then return false end + local basename = common.basename(info.filename) + -- replace '\' with '/' for Windows where PATHSEP = '\' + local fullname = "/" .. info.filename:gsub("\\", "/") + for _, compiled in ipairs(ignore_compiled) do + local test = compiled.use_path and fullname or basename + if compiled.match_dir then + if info.type == "dir" and string.match(test .. "/", compiled.pattern) then + return false + end + else + if string.match(test, compiled.pattern) then + return false + end + end + end + return true +end + + +local function compare_file(a, b) + return a.filename < b.filename +end + + +-- compute a file's info entry completed with "filename" to be used +-- in project scan or falsy if it shouldn't appear in the list. +local function get_project_file_info(root, file, ignore_compiled) + local info = system.get_file_info(root .. file) + -- info can be not nil but info.type may be nil if is neither a file neither + -- a directory, for example for /dev/* entries on linux. + if info and info.type then + info.filename = strip_leading_path(file) + return fileinfo_pass_filter(info, ignore_compiled) and info + end +end + + +-- "root" will by an absolute path without trailing '/' +-- "path" will be a path starting with '/' and without trailing '/' +-- or the empty string. +-- It will identifies a sub-path within "root. +-- The current path location will therefore always be: root .. path. +-- When recursing "root" will always be the same, only "path" will change. +-- Returns a list of file "items". In eash item the "filename" will be the +-- complete file path relative to "root" *without* the trailing '/'. +function dirwatch.get_directory_files(dir, root, path, t, entries_count, recurse_pred) + local t0 = system.get_time() + local all = system.list_dir(root .. path) or {} + local t_elapsed = system.get_time() - t0 + local dirs, files = {}, {} + local ignore_compiled = compile_ignore_files() + + for _, file in ipairs(all) do + local info = get_project_file_info(root, path .. PATHSEP .. file, ignore_compiled) + if info then + table.insert(info.type == "dir" and dirs or files, info) + entries_count = entries_count + 1 + end + end + + local recurse_complete = true + table.sort(dirs, compare_file) + for _, f in ipairs(dirs) do + table.insert(t, f) + if recurse_pred(dir, f.filename, entries_count, t_elapsed) then + local _, complete, n = dirwatch.get_directory_files(dir, root, PATHSEP .. f.filename, t, entries_count, recurse_pred) + recurse_complete = recurse_complete and complete + entries_count = n + else + recurse_complete = false + end + end + + table.sort(files, compare_file) + for _, f in ipairs(files) do + table.insert(t, f) + end + + return t, recurse_complete, entries_count +end + + +return dirwatch diff --git a/data/core/init.lua b/data/core/init.lua index b9b001e2..4b2b0ea0 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -5,6 +5,7 @@ local config = require "core.config" local style = require "core.style" local command local keymap +local dirwatch local RootView local StatusView local TitleView @@ -62,23 +63,6 @@ function core.set_project_dir(new_dir, change_project_fn) return chdir_ok end -function core.close_current_project() - -- When using system.unwatch_dir we need to pass the watch_id provided by dmon. - -- In reality when unwatching a directory the dmon library shifts the other watch_id - -- values so the actual watch_id changes. To workaround this problem we assume the - -- first watch_id is always 1 and the watch_id are continguous and we unwatch the - -- first watch_id repeateadly up to the number of watch_ids. - local watch_id_max = 0 - for _, project_dir in ipairs(core.project_directories) do - if project_dir.watch_id and project_dir.watch_id > watch_id_max then - watch_id_max = project_dir.watch_id - end - end - for i = 1, watch_id_max do - system.unwatch_dir(1) - end -end - local function reload_customizations() -- The logic is: @@ -124,134 +108,6 @@ local function strip_trailing_slash(filename) return filename end -local function compare_file(a, b) - return a.filename < b.filename -end - - --- inspect config.ignore_files patterns and prepare ready to use entries. -local function compile_ignore_files() - local ipatterns = config.ignore_files - local compiled = {} - -- config.ignore_files could be a simple string... - if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end - for i, pattern in ipairs(ipatterns) do - compiled[i] = { - use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end - -- An '/' or '/$' at the end means we want to match a directory. - match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value - pattern = pattern -- get the actual pattern - } - end - return compiled -end - - -local function safe_match(s, pattern) - local ok, match = pcall(string.match, s, pattern) - return ok and match -end - - -local function fileinfo_pass_filter(info, ignore_compiled) - if info.size >= config.file_size_limit * 1e6 then return false end - local basename = common.basename(info.filename) - -- replace '\' with '/' for Windows where PATHSEP = '\' - local fullname = "/" .. info.filename:gsub("\\", "/") - for _, compiled in ipairs(ignore_compiled) do - local test = compiled.use_path and fullname or basename - if compiled.match_dir then - if info.type == "dir" and safe_match(test .. "/", compiled.pattern) then - return false - end - else - if safe_match(test, compiled.pattern) then - return false - end - end - end - return true -end - - --- compute a file's info entry completed with "filename" to be used --- in project scan or falsy if it shouldn't appear in the list. -local function get_project_file_info(root, file, ignore_compiled) - local info = system.get_file_info(root .. file) - -- info can be not nil but info.type may be nil if is neither a file neither - -- a directory, for example for /dev/* entries on linux. - if info and info.type then - info.filename = strip_leading_path(file) - return fileinfo_pass_filter(info, ignore_compiled) and info - end -end - - --- Predicate function to inhibit directory recursion in get_directory_files --- based on a time limit and the number of files. -local function timed_max_files_pred(dir, filename, entries_count, t_elapsed) - local n_limit = entries_count <= config.max_project_files - local t_limit = t_elapsed < 20 / config.fps - return n_limit and t_limit and core.project_subdir_is_shown(dir, filename) -end - - --- "root" will by an absolute path without trailing '/' --- "path" will be a path starting with '/' and without trailing '/' --- or the empty string. --- It will identifies a sub-path within "root. --- The current path location will therefore always be: root .. path. --- When recursing "root" will always be the same, only "path" will change. --- Returns a list of file "items". In eash item the "filename" will be the --- complete file path relative to "root" *without* the trailing '/'. -local function get_directory_files(dir, root, path, t, ignore_compiled, entries_count, recurse_pred, begin_hook) - if begin_hook then begin_hook() end - ignore_compiled = ignore_compiled or compile_ignore_files() - local t0 = system.get_time() - local all = system.list_dir(root .. path) or {} - local t_elapsed = system.get_time() - t0 - local dirs, files = {}, {} - - for _, file in ipairs(all) do - local info = get_project_file_info(root, path .. PATHSEP .. file, ignore_compiled) - if info then - table.insert(info.type == "dir" and dirs or files, info) - entries_count = entries_count + 1 - end - end - - local recurse_complete = true - table.sort(dirs, compare_file) - for _, f in ipairs(dirs) do - table.insert(t, f) - if recurse_pred(dir, f.filename, entries_count, t_elapsed) then - local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, ignore_compiled, entries_count, recurse_pred, begin_hook) - recurse_complete = recurse_complete and complete - entries_count = n - else - recurse_complete = false - end - end - - table.sort(files, compare_file) - for _, f in ipairs(files) do - table.insert(t, f) - end - - return t, recurse_complete, entries_count -end - - -function core.project_subdir_set_show(dir, filename, show) - if dir.files_limit and not dir.force_rescan then - local fullpath = dir.name .. PATHSEP .. filename - if not (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath) then - return false - end - end - dir.shown_subdir[filename] = show - return true -end function core.project_subdir_is_shown(dir, filename) @@ -264,8 +120,10 @@ local function show_max_files_warning(dir) "Filesystem is too slow: project files will not be indexed." or "Too many files in project directory: stopped reading at ".. config.max_project_files.." files. For more information see ".. - "usage.md at github.com/lite-xl/lite-xl." - core.status_view:show_message("!", style.accent, message) + "usage.md at https://github.com/lite-xl/lite-xl." + if core.status_view then + core.status_view:show_message("!", style.accent, message) + end end @@ -328,28 +186,6 @@ local function files_list_replace(as, i1, n, bs, hook) end -local function project_scan_add_entry(dir, fileinfo) - assert(not dir.force_rescan, "should be used only when force_rescan is false") - local index, match = file_search(dir.files, fileinfo) - if not match then - table.insert(dir.files, index, fileinfo) - if fileinfo.type == "dir" and not dir.files_limit then - -- ASSUMPTION: dir.force_rescan is FALSE - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) - if fileinfo.symlink then - local new_files = get_directory_files(dir, dir.name, PATHSEP .. fileinfo.filename, {}, nil, 0, core.project_subdir_is_shown) - files_list_replace(dir.files, index, 0, new_files, {insert = function(info) - if info.type == "dir" then - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. info.filename) - end - end}) - end - end - dir.is_dirty = true - end -end - - local function project_subdir_bounds(dir, filename) local index, n = 0, #dir.files for i, file in ipairs(dir.files) do @@ -367,123 +203,148 @@ local function project_subdir_bounds(dir, filename) end end -local function rescan_project_subdir(dir, filename_rooted) - local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, nil, 0, core.project_subdir_is_shown, coroutine.yield) - local index, n = 0, #dir.files - if filename_rooted ~= "" then - local filename = strip_leading_path(filename_rooted) - index, n = project_subdir_bounds(dir, filename) - end - if not files_list_match(dir.files, index, n, new_files) then - -- Since we are modifying the list of files we may add new directories and - -- when dir.files_limit is false we need to add a watch for each subdirectory. - -- We are therefore passing a insert hook function to the purpose of adding - -- a watch. - -- Note that the hook shold almost never be called, it happens only if - -- we missed some directory creation event from the directory monitoring which - -- almost never happens. With inotify is at least theoretically possible. - local need_subdir_watches = not dir.files_limit and not dir.force_rescan - files_list_replace(dir.files, index, n, new_files, need_subdir_watches and {insert = function(fileinfo) - if fileinfo.type == "dir" then - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) - end - end}) - dir.is_dirty = true - return true - end +-- Predicate function to inhibit directory recursion in get_directory_files +-- based on a time limit and the number of files. +local function timed_max_files_pred(dir, filename, entries_count, t_elapsed) + local n_limit = entries_count <= config.max_project_files + local t_limit = t_elapsed < 20 / config.fps + return n_limit and t_limit and core.project_subdir_is_shown(dir, filename) end -local function add_dir_scan_thread(dir) - core.add_thread(function() - while true do - local has_changes = rescan_project_subdir(dir, "") - if has_changes then - core.redraw = true -- we run without an event, from a thread - end - coroutine.yield(5) - end - end, dir) -end - -local function folder_add_subdirs_watch(dir) - for _, fileinfo in ipairs(dir.files) do - if fileinfo.type == "dir" then - system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename) - end - end -end - - --- Populate a project folder top directory by scanning the filesystem. -local function scan_project_folder(index) - local dir = core.project_directories[index] - local fstype = system.get_fs_type(dir.name) - dir.force_rescan = (fstype == "nfs" or fstype == "fuse") - if not dir.force_rescan then - local watch_err - dir.watch_id, watch_err = system.watch_dir(dir.name) - if not dir.watch_id then - core.log("Watch directory %s: %s", dir.name, watch_err) - dir.force_rescan = true - end - end - local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, nil, 0, timed_max_files_pred) - -- If dir.files_limit is set to TRUE it means that: - -- * we will not read recursively all the project files and we don't index them - -- * we read only the files for the subdirectories that are opened/expanded in the - -- TreeView - -- * we add a subdirectory watch only to the directories that are opened/expanded - -- * we set the values in the shown_subdir table - -- - -- If dir.files_limit is set to FALSE it means that: - -- * we will read recursively all the project files and we index them - -- * all the list of project files is always complete and kept updated when - -- changes happen on the disk - -- * all the subdirectories at any depth must have a watch using system.watch_dir_add - -- * we DO NOT set the values in the shown_subdir table - -- - -- * If force_rescan is set to TRUE no watch are used in any case. - if not complete then - dir.slow_filesystem = not complete and (entries_count <= config.max_project_files) - dir.files_limit = true - if core.status_view then -- May be not yet initialized. - show_max_files_warning(dir) - end - end - dir.files = t - if dir.force_rescan then - add_dir_scan_thread(dir) +-- Should be called on any directory that registers a change. +-- Uses relative paths at the project root. +local function refresh_directory(topdir, target, expanded) + local index, n, directory + if target == "" then + index, n = 1, #topdir.files + directory = "" else - if not dir.files_limit then - folder_add_subdirs_watch(dir) + index, n = project_subdir_bounds(topdir, target) + index = index + 1 + n = index + n - 1 + directory = (PATHSEP .. target) + end + if index then + local files + local change = false + if topdir.files_limit then + -- If we have the folders literally open on the side panel. + files = expanded and dirwatch.get_directory_files(topdir, topdir.name, directory, {}, 0, core.project_subdir_is_shown) or {} + change = true + else + -- If we're expecting to keep track of everything, go through the list and iteratively deal with directories. + files = dirwatch.get_directory_files(topdir, topdir.name, directory, {}, 0, function() return false end) end - core.dir_rescan_add_job(dir, ".") + + local new_idx, old_idx = 1, index + local new_directories = {} + local last_dir = nil + while old_idx <= n or new_idx <= #files do + local old_info, new_info = topdir.files[old_idx], files[new_idx] + if not new_info or not old_info or not last_dir or old_info.filename:sub(1, #last_dir + 1) ~= last_dir .. "/" then + if not new_info or not old_info or not files_info_equal(new_info, old_info) then + change = true + if not old_info or (new_info and system.path_compare(new_info.filename, new_info.type, old_info.filename, old_info.type)) then + table.insert(topdir.files, old_idx, new_info) + new_idx = new_idx + 1 + old_idx = old_idx + 1 + if new_info.type == "dir" then + table.insert(new_directories, new_info) + end + n = n + 1 + else + table.remove(topdir.files, old_idx) + if old_info.type == "dir" then + topdir.watch:unwatch(target .. PATHSEP .. old_info.filename) + end + n = n - 1 + end + else + new_idx = new_idx + 1 + old_idx = old_idx + 1 + end + if old_info and old_info.type == "dir" then + last_dir = old_info.filename + end + else + old_idx = old_idx + 1 + end + end + for i, v in ipairs(new_directories) do + topdir.watch:watch(target) + if refresh_directory(topdir, target .. PATHSEP .. v.filename) then + change = true + end + end + if change then + core.redraw = true + topdir.is_dirty = true + end + return change end end - function core.add_project_directory(path) -- top directories has a file-like "item" but the item.filename -- will be simply the name of the directory, without its path. -- The field item.topdir will identify it as a top level directory. path = common.normalize_volume(path) - local dir = { + local topdir = { name = path, item = {filename = common.basename(path), type = "dir", topdir = true}, files_limit = false, is_dirty = true, shown_subdir = {}, + watch_thread = nil, + watch = dirwatch.new() } - table.insert(core.project_directories, dir) - scan_project_folder(#core.project_directories) + table.insert(core.project_directories, topdir) + + local fstype = PLATFORM == "Linux" and system.get_fs_type(topdir.name) or "unknown" + topdir.force_scans = (fstype == "nfs" or fstype == "fuse") + local t, complete, entries_count = dirwatch.get_directory_files(topdir, topdir.name, "", {}, 0, timed_max_files_pred) + topdir.files = t + if not complete then + topdir.slow_filesystem = not complete and (entries_count <= config.max_project_files) + topdir.files_limit = true + show_max_files_warning(topdir) + refresh_directory(topdir, "", true) + else + for i,v in ipairs(t) do + if v.type == "dir" then topdir.watch:watch(path .. PATHSEP .. v.filename) end + end + end + topdir.watch:watch(topdir.name) + -- each top level directory gets a watch thread. if the project is small, or + -- if the ablity to use directory watches hasn't been compromised in some way + -- either through error, or amount of files, then this should be incredibly + -- quick; essentially one syscall per check. Otherwise, this may take a bit of + -- time; the watch will yield in this coroutine after 0.01 second, for 0.1 seconds. + topdir.watch_thread = core.add_thread(function() + while true do + topdir.watch:check(function(target) + if target == topdir.name then return refresh_directory(topdir, "", true) end + local dirpath = target:sub(#topdir.name + 2) + local abs_dirpath = topdir.name .. PATHSEP .. dirpath + if dirpath then + -- check if the directory is in the project files list, if not exit. + local dir_index, dir_match = file_search(topdir.files, {filename = dirpath, type = "dir"}) + if not dir_match or not core.project_subdir_is_shown(topdir, topdir.files[dir_index].filename) then return end + end + return refresh_directory(topdir, dirpath, true) + end, 0.01, 0.01) + coroutine.yield(0.05) + end + end) + if path == core.project_dir then - core.project_files = dir.files + core.project_files = topdir.files end core.redraw = true - return dir + return topdir end @@ -496,7 +357,6 @@ local function rescan_project_directories() local dir = core.project_directories[i] save_project_dirs[i] = {name = dir.name, shown_subdir = dir.shown_subdir} end - core.close_current_project() -- ensure we unwatch directories core.project_directories = {} for i = 1, n do -- add again the directories in the project local dir = core.add_project_directory(save_project_dirs[i].name) @@ -520,7 +380,6 @@ local function rescan_project_directories() -- In theory set_show below may fail and return false but is it is listed -- there it means it succeeded before so we are optimistically assume it -- will not fail for the sake of simplicity. - core.project_subdir_set_show(dir, subdir, show) core.update_project_subdir(dir, subdir, show) break end @@ -542,14 +401,10 @@ end function core.update_project_subdir(dir, filename, expanded) assert(dir.files_limit, "function should be called only when directory is in files limit mode") + dir.shown_subdir[filename] = expanded local index, n, file = project_subdir_bounds(dir, filename) if index then - local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, nil, 0, core.project_subdir_is_shown) or {} - -- ASSUMPTION: core.update_project_subdir is called only when dir.files_limit is true - -- NOTE: we may add new directories below but we do not need to call - -- system.watch_dir_add because the current function is called only - -- in dir.files_limit mode and in this latter case we don't need to - -- add watch to new, unfolded, subdirectories. + local new_files = expanded and dirwatch.get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {} files_list_replace(dir.files, index, n, new_files) dir.is_dirty = true return true @@ -569,7 +424,7 @@ local function find_files_rec(root, path) info.filename = strip_leading_path(file) if info.type == "file" then coroutine.yield(root, info) - else + elseif not common.match_pattern(common.basename(info.filename), config.ignore_files) then find_files_rec(root, PATHSEP .. info.filename) end end @@ -630,62 +485,6 @@ function core.project_files_number() end -local function project_dir_by_watch_id(watch_id) - for i = 1, #core.project_directories do - if core.project_directories[i].watch_id == watch_id then - return core.project_directories[i] - end - end -end - - -local function project_scan_remove_file(dir, filepath) - local fileinfo = { filename = filepath } - for _, filetype in ipairs {"dir", "file"} do - fileinfo.type = filetype - local index, match = file_search(dir.files, fileinfo) - if match then - if filetype == "dir" then - -- If the directory is a symlink it may get deleted and we will - -- never get dirmonitor events for the removal the files it contains. - -- We proceed to remove all the files that belong to the directory. - local _, n_subdir = project_subdir_bounds(dir, filepath) - files_list_replace(dir.files, index, n_subdir, {}, { - remove= function(fileinfo) - if fileinfo.type == "dir" then - system.watch_dir_rm(dir.watch_id, dir.name .. PATHSEP .. filepath) - end - end}) - if dir.files_limit then - dir.shown_subdir[filepath] = nil - end - end - table.remove(dir.files, index) - dir.is_dirty = true - return - end - end -end - - -local function project_scan_add_file(dir, filepath) - local ignore = compile_ignore_files() - local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath, ignore) - if fileinfo then - -- on Windows and MacOS we can get events from directories we are not following: - -- check if each parent directories pass the ignore_files rules. - repeat - filepath = common.dirname(filepath) - local parent_info = filepath and get_project_file_info(dir.name, PATHSEP .. filepath, ignore) - if filepath and not parent_info then - return -- parent directory does match ignore_files rules: stop there - end - until not parent_info - project_scan_add_entry(dir, fileinfo) - end -end - - -- create a directory using mkdir but may need to create the parent -- directories as well. local function create_user_directory() @@ -859,6 +658,7 @@ end function core.init() command = require "core.command" keymap = require "core.keymap" + dirwatch = require "core.dirwatch" RootView = require "core.rootview" StatusView = require "core.statusview" TitleView = require "core.titleview" @@ -919,6 +719,8 @@ function core.init() core.threads = setmetatable({}, { __mode = "k" }) core.blink_start = system.get_time() core.blink_timer = core.blink_start + + local got_user_error, got_project_error = not core.load_user_directory() local project_dir_abs = system.absolute_path(project_dir) -- We prevent set_project_dir below to effectively add and scan the directory becaese tha @@ -933,7 +735,9 @@ function core.init() update_recents_project("remove", project_dir) end project_dir_abs = system.absolute_path(".") - if not core.set_project_dir(project_dir_abs) then + if not core.set_project_dir(project_dir_abs, function() + got_project_error = not core.load_project_module() + end) then system.show_fatal_error("Lite XL internal error", "cannot set project directory to cwd") os.exit(1) end @@ -960,14 +764,12 @@ function core.init() cur_node = cur_node:split("down", core.status_view, {y = true}) command.add_defaults() - local got_user_error = not core.load_user_directory() local plugins_success, plugins_refuse_list = core.load_plugins() do local pdir, pname = project_dir_abs:match("(.*)[/\\\\](.*)") core.log("Opening project %q from directory %s", pname, pdir) end - local got_project_error = not core.load_project_module() -- We add the project directory now because the project's module is loaded. core.add_project_directory(project_dir_abs) @@ -1381,78 +1183,6 @@ function core.has_pending_rescan() end end - -function core.dir_rescan_add_job(dir, filepath) - local dirpath = filepath:match("^(.+)[/\\].+$") - local dirpath_rooted = dirpath and PATHSEP .. dirpath or "" - local abs_dirpath = dir.name .. dirpath_rooted - if dirpath then - -- check if the directory is in the project files list, if not exit - local dir_index, dir_match = file_search(dir.files, {filename = dirpath, type = "dir"}) - -- Note that is dir_match is false dir_index greaten than the last valid index. - -- We use dir_index to index dir.files below only if dir_match is true. - if not dir_match or not core.project_subdir_is_shown(dir, dir.files[dir_index].filename) then return end - end - local new_time = system.get_time() + 1 - - -- evaluate new rescan request versus existing rescan - local remove_list = {} - for _, rescan in pairs(scheduled_rescan) do - if abs_dirpath == rescan.abs_path or common.path_belongs_to(abs_dirpath, rescan.abs_path) then - -- abs_dirpath is a subpath of a scan already ongoing: skip - rescan.time_limit = new_time - return - elseif common.path_belongs_to(rescan.abs_path, abs_dirpath) then - -- abs_dirpath already cover this rescan: add to the list of rescan to be removed - table.insert(remove_list, rescan.abs_path) - end - end - for _, key_path in ipairs(remove_list) do - scheduled_rescan[key_path] = nil - end - - scheduled_rescan[abs_dirpath] = {dir = dir, path = dirpath_rooted, abs_path = abs_dirpath, time_limit = new_time} - core.add_thread(function() - while true do - local rescan = scheduled_rescan[abs_dirpath] - if not rescan then return end - if system.get_time() > rescan.time_limit then - local has_changes = rescan_project_subdir(rescan.dir, rescan.path) - if has_changes then - core.redraw = true -- we run without an event, from a thread - rescan.time_limit = new_time - else - scheduled_rescan[rescan.abs_path] = nil - return - end - end - coroutine.yield(0.2) - end - end) -end - - --- no-op but can be overrided by plugins -function core.on_dirmonitor_modify(dir, filepath) end -function core.on_dirmonitor_delete(dir, filepath) end - - -function core.on_dir_change(watch_id, action, filepath) - local dir = project_dir_by_watch_id(watch_id) - if not dir then return end - core.dir_rescan_add_job(dir, filepath) - if action == "delete" then - project_scan_remove_file(dir, filepath) - core.on_dirmonitor_delete(dir, filepath) - elseif action == "create" then - project_scan_add_file(dir, filepath) - core.on_dirmonitor_modify(dir, filepath); - elseif action == "modify" then - core.on_dirmonitor_modify(dir, filepath); - end -end - - function core.on_event(type, ...) local did_keymap = false if type == "textinput" then @@ -1494,8 +1224,6 @@ function core.on_event(type, ...) end elseif type == "focuslost" then core.root_view:on_focus_lost(...) - elseif type == "dirchange" then - core.on_dir_change(...) elseif type == "quit" then core.quit() end diff --git a/data/plugins/treeview.lua b/data/plugins/treeview.lua index 6a48dfe2..6841586d 100644 --- a/data/plugins/treeview.lua +++ b/data/plugins/treeview.lua @@ -236,9 +236,6 @@ function TreeView:on_mouse_pressed(button, x, y, clicks) else local hovered_dir = core.project_dir_by_name(hovered_item.dir_name) if hovered_dir and hovered_dir.files_limit then - if not core.project_subdir_set_show(hovered_dir, hovered_item.filename, not hovered_item.expanded) then - return - end core.update_project_subdir(hovered_dir, hovered_item.filename, not hovered_item.expanded) end hovered_item.expanded = not hovered_item.expanded @@ -592,7 +589,7 @@ command.add(function() return view.hovered_item ~= nil end, { system.exec(string.format("start \"\" %q", hovered_item.abs_filename)) elseif string.find(PLATFORM, "Mac") then system.exec(string.format("open %q", hovered_item.abs_filename)) - elseif PLATFORM == "Linux" then + elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then system.exec(string.format("xdg-open %q", hovered_item.abs_filename)) end end, diff --git a/lib/dmon/dmon.h b/lib/dmon/dmon.h deleted file mode 100644 index 84258bad..00000000 --- a/lib/dmon/dmon.h +++ /dev/null @@ -1,1685 +0,0 @@ -#ifndef __DMON_H__ -#define __DMON_H__ - -// -// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved. -// License: https://github.com/septag/dmon#license-bsd-2-clause -// -// Portable directory monitoring library -// watches directories for file or directory changes. -// -// Usage: -// define DMON_IMPL and include this file to use it: -// #define DMON_IMPL -// #include "dmon.h" -// -// dmon_init(): -// Call this once at the start of your program. -// This will start a low-priority monitoring thread -// dmon_deinit(): -// Call this when your work with dmon is finished, usually on program terminate -// This will free resources and stop the monitoring thread -// dmon_watch: -// Watch for directories -// You can watch multiple directories by calling this function multiple times -// rootdir: root directory to monitor -// watch_cb: callback function to receive events. -// NOTE that this function is called from another thread, so you should -// beware of data races in your application when accessing data within this -// callback -// flags: watch flags, see dmon_watch_flags_t -// user_data: user pointer that is passed to callback function -// Returns the Id of the watched directory after successful call, or returns Id=0 if error -// dmon_unwatch: -// Remove the directory from watch list -// -// see test.c for the basic example -// -// Configuration: -// You can customize some low-level functionality like malloc and logging by overriding macros: -// -// DMON_MALLOC, DMON_FREE, DMON_REALLOC: -// define these macros to override memory allocations -// default is 'malloc', 'free' and 'realloc' -// DMON_ASSERT: -// define this to provide your own assert -// default is 'assert' -// DMON_LOG_DEBUG -// define this to provide your own extra debug logging mechanism -// default implementation logs to stdout in DEBUG and does nothing in other builds -// DMON_API_DECL, DMON_API_IMPL -// define these to provide your own API declerations. (for example: static) -// default is nothing (which is extern in C language ) -// DMON_MAX_PATH -// Maximum size of path characters -// default is 260 characters -// DMON_MAX_WATCHES -// Maximum number of watch directories -// default is 64 -// -// TODO: -// - DMON_WATCHFLAGS_FOLLOW_SYMLINKS does not resolve files -// - implement DMON_WATCHFLAGS_OUTOFSCOPE_LINKS -// - implement DMON_WATCHFLAGS_IGNORE_DIRECTORIES -// -// History: -// 1.0.0 First version. working Win32/Linux backends -// 1.1.0 MacOS backend -// 1.1.1 Minor fixes, eliminate gcc/clang warnings with -Wall -// 1.1.2 Eliminate some win32 dead code -// 1.1.3 Fixed select not resetting causing high cpu usage on linux -// 1.2.1 inotify (linux) fixes and improvements, added extra functionality header for linux -// to manually add/remove directories manually to the watch handle, in case of large file sets -// - -#include -#include - -#ifndef DMON_API_DECL -# define DMON_API_DECL -#endif - -#ifndef DMON_API_IMPL -# define DMON_API_IMPL -#endif - -typedef struct { uint32_t id; } dmon_watch_id; - -// Pass these flags to `dmon_watch` -typedef enum dmon_watch_flags_t { - DMON_WATCHFLAGS_RECURSIVE = 0x1, // monitor all child directories - DMON_WATCHFLAGS_FOLLOW_SYMLINKS = 0x2, // resolve symlinks (linux only) - DMON_WATCHFLAGS_OUTOFSCOPE_LINKS = 0x4, // TODO: not implemented yet - DMON_WATCHFLAGS_IGNORE_DIRECTORIES = 0x8 // TODO: not implemented yet -} dmon_watch_flags; - -// Action is what operation performed on the file. this value is provided by watch callback -typedef enum dmon_action_t { - DMON_ACTION_CREATE = 1, - DMON_ACTION_DELETE, - DMON_ACTION_MODIFY, - DMON_ACTION_MOVE -} dmon_action; - -typedef enum dmon_error_enum { - DMON_SUCCESS = 0, - DMON_ERROR_WATCH_DIR, - DMON_ERROR_OPEN_DIR, - DMON_ERROR_MONITOR_FAIL, - DMON_ERROR_UNSUPPORTED_SYMLINK, - DMON_ERROR_SUBDIR_LOCATION, - DMON_ERROR_END -} dmon_error; - -#ifdef __cplusplus -extern "C" { -#endif - -DMON_API_DECL const char *dmon_error_str(dmon_error err); - -DMON_API_DECL void dmon_init(void); -DMON_API_DECL void dmon_deinit(void); - -DMON_API_DECL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* rootdir, const char* filepath, - const char* oldfilepath, void* user), - uint32_t flags, void* user_data, dmon_error *error_code); -DMON_API_DECL void dmon_unwatch(dmon_watch_id id); - -#ifdef __cplusplus -} -#endif - -#ifdef DMON_IMPL - -#define DMON_OS_WINDOWS 0 -#define DMON_OS_MACOS 0 -#define DMON_OS_LINUX 0 - -#if defined(_WIN32) || defined(_WIN64) -# undef DMON_OS_WINDOWS -# define DMON_OS_WINDOWS 1 -#elif defined(__linux__) -# undef DMON_OS_LINUX -# define DMON_OS_LINUX 1 -#elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) -# undef DMON_OS_MACOS -# define DMON_OS_MACOS __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#else -# define DMON_OS 0 -# error "unsupported platform" -#endif - -#if DMON_OS_WINDOWS -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# ifndef NOMINMAX -# define NOMINMAX -# endif -# include -#elif DMON_OS_LINUX -# ifndef __USE_MISC -# define __USE_MISC -# endif -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -/* Recursive removed for Lite XL when using inotify. */ -# define LITE_XL_DISABLE_INOTIFY_RECURSIVE -# define DMON_LOG_DEBUG(s) -#elif DMON_OS_MACOS -# include -# include -# include -# include -# include -#endif - -#ifndef DMON_MALLOC -# include -# define DMON_MALLOC(size) malloc(size) -# define DMON_FREE(ptr) free(ptr) -# define DMON_REALLOC(ptr, size) realloc(ptr, size) -#endif - -#ifndef DMON_ASSERT -# include -# define DMON_ASSERT(e) assert(e) -#endif - -#ifndef DMON_LOG_DEBUG -# ifndef NDEBUG -# include -# define DMON_LOG_DEBUG(s) do { puts(s); } while(0) -# else -# define DMON_LOG_DEBUG(s) -# endif -#endif - -#ifndef DMON_MAX_WATCHES -# define DMON_MAX_WATCHES 64 -#endif - -#ifndef DMON_MAX_PATH -# define DMON_MAX_PATH 260 -#endif - -#define _DMON_UNUSED(x) (void)(x) - -#ifndef _DMON_PRIVATE -# if defined(__GNUC__) || defined(__clang__) -# define _DMON_PRIVATE __attribute__((unused)) static -# else -# define _DMON_PRIVATE static -# endif -#endif - -#include - -#ifndef _DMON_LOG_DEBUGF -# define _DMON_LOG_DEBUGF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_DEBUG(msg); } while(0); -#endif - -#ifndef dmon__min -# define dmon__min(a, b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef dmon__max -# define dmon__max(a, b) ((a) > (b) ? (a) : (b)) -#endif - -#ifndef dmon__swap -# define dmon__swap(a, b, _type) \ - do { \ - _type tmp = a; \ - a = b; \ - b = tmp; \ - } while (0) -#endif - -#ifndef dmon__make_id -# ifdef __cplusplus -# define dmon__make_id(id) {id} -# else -# define dmon__make_id(id) (dmon_watch_id) {id} -# endif -#endif // dmon__make_id - -_DMON_PRIVATE bool dmon__isrange(char ch, char from, char to) -{ - return (uint8_t)(ch - from) <= (uint8_t)(to - from); -} - -_DMON_PRIVATE bool dmon__isupperchar(char ch) -{ - return dmon__isrange(ch, 'A', 'Z'); -} - -_DMON_PRIVATE char dmon__tolowerchar(char ch) -{ - return ch + (dmon__isupperchar(ch) ? 0x20 : 0); -} - -_DMON_PRIVATE char* dmon__tolower(char* dst, int dst_sz, const char* str) -{ - int offset = 0; - int dst_max = dst_sz - 1; - while (*str && offset < dst_max) { - dst[offset++] = dmon__tolowerchar(*str); - ++str; - } - dst[offset] = '\0'; - return dst; -} - -_DMON_PRIVATE char* dmon__strcpy(char* dst, int dst_sz, const char* src) -{ - DMON_ASSERT(dst); - DMON_ASSERT(src); - - const int32_t len = (int32_t)strlen(src); - const int32_t _max = dst_sz - 1; - const int32_t num = (len < _max ? len : _max); - memcpy(dst, src, num); - dst[num] = '\0'; - - return dst; -} - -_DMON_PRIVATE char* dmon__unixpath(char* dst, int size, const char* path) -{ - size_t len = strlen(path); - len = dmon__min(len, (size_t)size - 1); - - for (size_t i = 0; i < len; i++) { - if (path[i] != '\\') - dst[i] = path[i]; - else - dst[i] = '/'; - } - dst[len] = '\0'; - return dst; -} - -#if DMON_OS_LINUX || DMON_OS_MACOS -_DMON_PRIVATE char* dmon__strcat(char* dst, int dst_sz, const char* src) -{ - int len = (int)strlen(dst); - return dmon__strcpy(dst + len, dst_sz - len, src); -} -#endif // DMON_OS_LINUX || DMON_OS_MACOS - -// stretchy buffer: https://github.com/nothings/stb/blob/master/stretchy_buffer.h -#define stb_sb_free(a) ((a) ? DMON_FREE(stb__sbraw(a)),0 : 0) -#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) -#define stb_sb_pop(a) (stb__sbn(a)--) -#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0) -#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) -#define stb_sb_last(a) ((a)[stb__sbn(a)-1]) -#define stb_sb_reset(a) ((a) ? (stb__sbn(a) = 0) : 0) - -#define stb__sbraw(a) ((int *) (a) - 2) -#define stb__sbm(a) stb__sbraw(a)[0] -#define stb__sbn(a) stb__sbraw(a)[1] - -#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a)) -#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) -#define stb__sbgrow(a,n) (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a)))) - -static void * stb__sbgrowf(void *arr, int increment, int itemsize) -{ - int dbl_cur = arr ? 2*stb__sbm(arr) : 0; - int min_needed = stb_sb_count(arr) + increment; - int m = dbl_cur > min_needed ? dbl_cur : min_needed; - int *p = (int *) DMON_REALLOC(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2); - if (p) { - if (!arr) - p[1] = 0; - p[0] = m; - return p+2; - } else { - return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later - } -} - -// watcher callback (same as dmon.h's decleration) -typedef void (dmon__watch_cb)(dmon_watch_id, dmon_action, const char*, const char*, const char*, void*); - -static const char *dmon__errors[] = { - "Success", - "Error watching directory", - "Error opening directory", - "Error enabling monitoring", - "Error support for symlink disabled", - "Error not a subdirectory", -}; - -DMON_API_IMPL const char *dmon_error_str(dmon_error err) { - DMON_ASSERT(err >= 0 && err < DMON_ERROR_END); - return dmon__errors[(int) err]; -} - -#if DMON_OS_WINDOWS -// IOCP (windows) -#ifdef UNICODE -# define _DMON_WINAPI_STR(name, size) wchar_t _##name[size]; MultiByteToWideChar(CP_UTF8, 0, name, -1, _##name, size) -#else -# define _DMON_WINAPI_STR(name, size) const char* _##name = name -#endif - -typedef struct dmon__win32_event { - char filepath[DMON_MAX_PATH]; - DWORD action; - dmon_watch_id watch_id; - bool skip; -} dmon__win32_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - OVERLAPPED overlapped; - HANDLE dir_handle; - uint8_t buffer[64512]; // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx - DWORD notify_filter; - dmon__watch_cb* watch_cb; - uint32_t watch_flags; - void* user_data; - char rootdir[DMON_MAX_PATH]; - char old_filepath[DMON_MAX_PATH]; -} dmon__watch_state; - -typedef struct dmon__state { - int num_watches; - dmon__watch_state watches[DMON_MAX_WATCHES]; - HANDLE thread_handle; - CRITICAL_SECTION mutex; - volatile int modify_watches; - CRITICAL_SECTION modify_watches_mutex; - dmon__win32_event* events; - bool quit; - HANDLE wake_event; -} dmon__state; - -static bool _dmon_init; -static dmon__state _dmon; - -_DMON_PRIVATE bool dmon__refresh_watch(dmon__watch_state* watch) -{ - return ReadDirectoryChangesW(watch->dir_handle, watch->buffer, sizeof(watch->buffer), - (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) ? TRUE : FALSE, - watch->notify_filter, NULL, &watch->overlapped, NULL) != 0; -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - CancelIo(watch->dir_handle); - CloseHandle(watch->overlapped.hEvent); - CloseHandle(watch->dir_handle); - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -_DMON_PRIVATE void dmon__win32_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__win32_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - if (ev->action == FILE_ACTION_MODIFIED || ev->action == FILE_ACTION_ADDED) { - // remove duplicate modifies on a single file - for (int j = i + 1; j < c; j++) { - dmon__win32_event* check_ev = &_dmon.events[j]; - if (check_ev->action == FILE_ACTION_MODIFIED && - strcmp(ev->filepath, check_ev->filepath) == 0) { - check_ev->skip = true; - } - } - } - } - - // trigger user callbacks - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__win32_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - switch (ev->action) { - case FILE_ACTION_ADDED: - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - case FILE_ACTION_MODIFIED: - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - case FILE_ACTION_RENAMED_OLD_NAME: { - // find the first occurance of the NEW_NAME - // this is somewhat API flaw that we have no reference for relating old and new files - for (int j = i + 1; j < c; j++) { - dmon__win32_event* check_ev = &_dmon.events[j]; - if (check_ev->action == FILE_ACTION_RENAMED_NEW_NAME) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } break; - case FILE_ACTION_REMOVED: - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - } - } - stb_sb_reset(_dmon.events); -} - -static int dmon__safe_get_modify_watches() { - EnterCriticalSection(&_dmon.modify_watches_mutex); - const int value = _dmon.modify_watches; - LeaveCriticalSection(&_dmon.modify_watches_mutex); - return value; -} - -_DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) -{ - _DMON_UNUSED(arg); - HANDLE wait_handles[DMON_MAX_WATCHES + 1]; - - SYSTEMTIME starttm; - GetSystemTime(&starttm); - uint64_t msecs_elapsed = 0; - - while (!_dmon.quit) { - if (dmon__safe_get_modify_watches() || - !TryEnterCriticalSection(&_dmon.mutex)) { - Sleep(10); - continue; - } - - if (_dmon.num_watches == 0) { - Sleep(10); - LeaveCriticalSection(&_dmon.mutex); - continue; - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - wait_handles[i] = watch->overlapped.hEvent; - } - - const int n = _dmon.num_watches; - wait_handles[n] = _dmon.wake_event; - const int n_pending = stb_sb_count(_dmon.events); - DWORD wait_result = WaitForMultipleObjects(n + 1, wait_handles, FALSE, n_pending > 0 ? 10 : INFINITE); - // NOTE: maybe we should check for WAIT_ABANDONED_ values if that can happen. - if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + n) { - dmon__watch_state* watch = &_dmon.watches[wait_result - WAIT_OBJECT_0]; - - DWORD bytes; - if (HasOverlappedIoCompleted(&watch->overlapped) && - GetOverlappedResult(watch->dir_handle, &watch->overlapped, &bytes, FALSE)) { - char filepath[DMON_MAX_PATH]; - PFILE_NOTIFY_INFORMATION notify; - size_t offset = 0; - - if (bytes == 0) { - dmon__refresh_watch(watch); - LeaveCriticalSection(&_dmon.mutex); - continue; - } - - do { - notify = (PFILE_NOTIFY_INFORMATION)&watch->buffer[offset]; - - int count = WideCharToMultiByte(CP_UTF8, 0, notify->FileName, - notify->FileNameLength / sizeof(WCHAR), - filepath, DMON_MAX_PATH - 1, NULL, NULL); - filepath[count] = TEXT('\0'); - dmon__unixpath(filepath, sizeof(filepath), filepath); - - // TODO: ignore directories if flag is set - - if (stb_sb_count(_dmon.events) == 0) { - msecs_elapsed = 0; - } - dmon__win32_event wev = { { 0 }, notify->Action, watch->id, false }; - dmon__strcpy(wev.filepath, sizeof(wev.filepath), filepath); - stb_sb_push(_dmon.events, wev); - - offset += notify->NextEntryOffset; - } while (notify->NextEntryOffset > 0); - - if (!_dmon.quit) { - dmon__refresh_watch(watch); - } - } - } // if (WaitForMultipleObjects) - - SYSTEMTIME tm; - GetSystemTime(&tm); - LONG dt = - (tm.wSecond - starttm.wSecond) * 1000 + (tm.wMilliseconds - starttm.wMilliseconds); - starttm = tm; - msecs_elapsed += dt; - if (msecs_elapsed > 100 && stb_sb_count(_dmon.events) > 0) { - dmon__win32_process_events(); - msecs_elapsed = 0; - } - - LeaveCriticalSection(&_dmon.mutex); - } - return 0; -} - - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - InitializeCriticalSection(&_dmon.mutex); - InitializeCriticalSection(&_dmon.modify_watches_mutex); - - _dmon.thread_handle = - CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dmon__thread, NULL, 0, NULL); - _dmon.wake_event = CreateEvent(NULL, FALSE, FALSE, NULL); - DMON_ASSERT(_dmon.thread_handle); - _dmon_init = true; -} - -static void dmon__enter_critical_wakeup(void) { - EnterCriticalSection(&_dmon.modify_watches_mutex); - _dmon.modify_watches = 1; - if (TryEnterCriticalSection(&_dmon.mutex) == 0) { - SetEvent(_dmon.wake_event); - EnterCriticalSection(&_dmon.mutex); - } - LeaveCriticalSection(&_dmon.modify_watches_mutex); -} - -static void dmon__leave_critical_wakeup(void) { - EnterCriticalSection(&_dmon.modify_watches_mutex); - _dmon.modify_watches = 0; - LeaveCriticalSection(&_dmon.modify_watches_mutex); - LeaveCriticalSection(&_dmon.mutex); -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - dmon__enter_critical_wakeup(); - if (_dmon.thread_handle != INVALID_HANDLE_VALUE) { - WaitForSingleObject(_dmon.thread_handle, INFINITE); - CloseHandle(_dmon.thread_handle); - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - dmon__leave_critical_wakeup(); - DeleteCriticalSection(&_dmon.mutex); - DeleteCriticalSection(&_dmon.modify_watches_mutex); - stb_sb_free(_dmon.events); - _dmon_init = false; -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data, dmon_error *error_code) -{ - DMON_ASSERT(watch_cb); - DMON_ASSERT(rootdir && rootdir[0]); - - dmon__enter_critical_wakeup(); - - DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - dmon__unixpath(watch->rootdir, sizeof(watch->rootdir), rootdir); - size_t rootdir_len = strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - _DMON_WINAPI_STR(rootdir, DMON_MAX_PATH); - watch->dir_handle = - CreateFile(_rootdir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); - if (watch->dir_handle != INVALID_HANDLE_VALUE) { - watch->notify_filter = FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_LAST_WRITE | - FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_SIZE; - watch->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - if (watch->overlapped.hEvent == INVALID_HANDLE_VALUE || - !dmon__refresh_watch(watch)) { - dmon__unwatch(watch); - *error_code = DMON_ERROR_WATCH_DIR; - dmon__leave_critical_wakeup(); - return dmon__make_id(0); - } - } else { - *error_code = DMON_ERROR_OPEN_DIR; - dmon__leave_critical_wakeup(); - return dmon__make_id(0); - } - - dmon__leave_critical_wakeup(); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - dmon__enter_critical_wakeup(); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - dmon__leave_critical_wakeup(); -} - -#elif DMON_OS_LINUX -// inotify linux backend -#define _DMON_TEMP_BUFFSIZE ((sizeof(struct inotify_event) + PATH_MAX) * 1024) - -typedef struct dmon__watch_subdir { - char rootdir[DMON_MAX_PATH]; -} dmon__watch_subdir; - -typedef struct dmon__inotify_event { - char filepath[DMON_MAX_PATH]; - uint32_t mask; - uint32_t cookie; - dmon_watch_id watch_id; - bool skip; -} dmon__inotify_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - int fd; - uint32_t watch_flags; - dmon__watch_cb* watch_cb; - void* user_data; - char rootdir[DMON_MAX_PATH]; - dmon__watch_subdir* subdirs; - int* wds; -} dmon__watch_state; - -typedef struct dmon__state { - dmon__watch_state watches[DMON_MAX_WATCHES]; - dmon__inotify_event* events; - int num_watches; - pthread_t thread_handle; - pthread_mutex_t mutex; - volatile int wait_flag; - pthread_mutex_t wait_flag_mutex; - int wake_event_pipe[2]; - bool quit; -} dmon__state; - -static bool _dmon_init; -static dmon__state _dmon; - -/* Implementation of recursive monitoring was removed on Linux for the Lite XL - * application. It is never used with recent version of Lite XL starting from 2.0.5 - * and recursive monitoring with inotify was always problematic and half-broken. - * Do not cover the new calling signature with error_code because not used by - * Lite XL. */ -#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE -_DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t mask, - bool followlinks, dmon__watch_state* watch) -{ - struct dirent* entry; - DIR* dir = opendir(dirname); - DMON_ASSERT(dir); - - char watchdir[DMON_MAX_PATH]; - - while ((entry = readdir(dir)) != NULL) { - bool entry_valid = false; - if (entry->d_type == DT_DIR) { - if (strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, ".") != 0) { - dmon__strcpy(watchdir, sizeof(watchdir), dirname); - dmon__strcat(watchdir, sizeof(watchdir), entry->d_name); - entry_valid = true; - } - } else if (followlinks && entry->d_type == DT_LNK) { - char linkpath[PATH_MAX]; - dmon__strcpy(watchdir, sizeof(watchdir), dirname); - dmon__strcat(watchdir, sizeof(watchdir), entry->d_name); - char* r = realpath(watchdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - dmon__strcpy(watchdir, sizeof(watchdir), linkpath); - entry_valid = true; - } - - // add sub-directory to watch dirs - if (entry_valid) { - int watchdir_len = (int)strlen(watchdir); - if (watchdir[watchdir_len - 1] != '/') { - watchdir[watchdir_len] = '/'; - watchdir[watchdir_len + 1] = '\0'; - } - int wd = inotify_add_watch(fd, watchdir, mask); - _DMON_UNUSED(wd); - DMON_ASSERT(wd != -1); - - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // recurse - dmon__watch_recursive(watchdir, fd, mask, followlinks, watch); - } - } - closedir(dir); -} -#endif - -_DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int wd) -{ - const int* wds = watch->wds; - for (int i = 0, c = stb_sb_count(wds); i < c; i++) { - if (wd == wds[i]) { - return watch->subdirs[i].rootdir; - } - } - - return NULL; -} - -#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE -_DMON_PRIVATE void dmon__gather_recursive(dmon__watch_state* watch, const char* dirname) -{ - struct dirent* entry; - DIR* dir = opendir(dirname); - DMON_ASSERT(dir); - - char newdir[DMON_MAX_PATH]; - while ((entry = readdir(dir)) != NULL) { - bool entry_valid = false; - bool is_dir = false; - if (strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, ".") != 0) { - dmon__strcpy(newdir, sizeof(newdir), dirname); - dmon__strcat(newdir, sizeof(newdir), entry->d_name); - is_dir = (entry->d_type == DT_DIR); - entry_valid = true; - } - - // add sub-directory to watch dirs - if (entry_valid) { - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), newdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), newdir + strlen(watch->rootdir)); - } - - dmon__inotify_event dev = { { 0 }, IN_CREATE|(is_dir ? IN_ISDIR : 0), 0, watch->id, false }; - dmon__strcpy(dev.filepath, sizeof(dev.filepath), subdir.rootdir); - stb_sb_push(_dmon.events, dev); - } - } - closedir(dir); -} -#endif - -_DMON_PRIVATE void dmon__inotify_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__inotify_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - // remove redundant modify events on a single file - if (ev->mask & IN_MODIFY) { - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } else if ((ev->mask & IN_ISDIR) && (check_ev->mask & (IN_ISDIR|IN_MODIFY))) { - // in some cases, particularly when created files under sub directories - // there can be two modify events for a single subdir one with trailing slash and one without - // remove traling slash from both cases and test - int l1 = (int)strlen(ev->filepath); - int l2 = (int)strlen(check_ev->filepath); - if (ev->filepath[l1-1] == '/') ev->filepath[l1-1] = '\0'; - if (check_ev->filepath[l2-1] == '/') check_ev->filepath[l2-1] = '\0'; - if (strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } - } - } - } else if (ev->mask & IN_CREATE) { - bool loop_break = false; - for (int j = i + 1; j < c && !loop_break; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if ((check_ev->mask & IN_MOVED_FROM) && strcmp(ev->filepath, check_ev->filepath) == 0) { - // there is a case where some programs (like gedit): - // when we save, it creates a temp file, and moves it to the file being modified - // search for these cases and remove all of them - for (int k = j + 1; k < c; k++) { - dmon__inotify_event* third_ev = &_dmon.events[k]; - if (third_ev->mask & IN_MOVED_TO && check_ev->cookie == third_ev->cookie) { - third_ev->mask = IN_MODIFY; // change to modified - ev->skip = check_ev->skip = true; - loop_break = true; - break; - } - } - } else if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) { - // Another case is that file is copied. CREATE and MODIFY happens sequentially - // so we ignore MODIFY event - check_ev->skip = true; - } - } - } else if (ev->mask & IN_MOVED_FROM) { - bool move_valid = false; - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) { - move_valid = true; - break; - } - } - - // in some environments like nautilus file explorer: - // when a file is deleted, it is moved to recycle bin - // so if the destination of the move is not valid, it's probably DELETE - if (!move_valid) { - ev->mask = IN_DELETE; - } - } else if (ev->mask & IN_MOVED_TO) { - bool move_valid = false; - for (int j = 0; j < i; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_FROM && ev->cookie == check_ev->cookie) { - move_valid = true; - break; - } - } - - // in some environments like nautilus file explorer: - // when a file is deleted, it is moved to recycle bin, on undo it is moved back it - // so if the destination of the move is not valid, it's probably CREATE - if (!move_valid) { - ev->mask = IN_CREATE; - } - } else if (ev->mask & IN_DELETE) { - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - // if the file is DELETED and then MODIFIED after, just ignore the modify event - if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) { - check_ev->skip = true; - break; - } - } - } - } - - // trigger user callbacks - for (int i = 0; i < stb_sb_count(_dmon.events); i++) { - dmon__inotify_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - if (ev->mask & IN_CREATE) { -# ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE - if (ev->mask & IN_ISDIR) { - if (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) { - char watchdir[DMON_MAX_PATH]; - dmon__strcpy(watchdir, sizeof(watchdir), watch->rootdir); - dmon__strcat(watchdir, sizeof(watchdir), ev->filepath); - dmon__strcat(watchdir, sizeof(watchdir), "/"); - uint32_t mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - int wd = inotify_add_watch(watch->fd, watchdir, mask); - // Removing the assertion below because it was giving errors for some reason - // when building a new package. - // _DMON_UNUSED(wd); - // DMON_ASSERT(wd != -1); - if (wd == -1) continue; - - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // some directories may be already created, for instance, with the command: mkdir -p - // so we will enumerate them manually and add them to the events - dmon__gather_recursive(watch, watchdir); - ev = &_dmon.events[i]; // gotta refresh the pointer because it may be relocated - } - } -# endif - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - else if (ev->mask & IN_MODIFY) { - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - else if (ev->mask & IN_MOVED_FROM) { - for (int j = i + 1; j < stb_sb_count(_dmon.events); j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } - else if (ev->mask & IN_DELETE) { - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - } - - stb_sb_reset(_dmon.events); -} - -_DMON_PRIVATE int dmon__safe_get_wait_flag() { - pthread_mutex_lock(&_dmon.wait_flag_mutex); - const int value = _dmon.wait_flag; - pthread_mutex_unlock(&_dmon.wait_flag_mutex); - return value; -} - -static void* dmon__thread(void* arg) -{ - _DMON_UNUSED(arg); - - static uint8_t buff[_DMON_TEMP_BUFFSIZE]; - struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; - struct timespec rem = { 0, 0 }; - struct timeval timeout; - uint64_t usecs_elapsed = 0; - - struct timeval starttm; - gettimeofday(&starttm, 0); - - while (!_dmon.quit) { - nanosleep(&req, &rem); - if (_dmon.num_watches == 0 || - dmon__safe_get_wait_flag() || - pthread_mutex_trylock(&_dmon.mutex) != 0) { - continue; - } - - // Create read FD set - fd_set rfds; - FD_ZERO(&rfds); - const int n = _dmon.num_watches; - int nfds = 0; - for (int i = 0; i < n; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - FD_SET(watch->fd, &rfds); - if (watch->fd > nfds) - nfds = watch->fd; - } - int wake_fd = _dmon.wake_event_pipe[0]; - FD_SET(wake_fd, &rfds); - if (wake_fd > nfds) - nfds = wake_fd; - - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - const int n_pending = stb_sb_count(_dmon.events); - if (select(nfds + 1, &rfds, NULL, NULL, n_pending > 0 ? &timeout : NULL)) { - if (FD_ISSET(wake_fd, &rfds)) { - char read_char; - read(wake_fd, &read_char, 1); - } - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - if (FD_ISSET(watch->fd, &rfds)) { - ssize_t offset = 0; - ssize_t len = read(watch->fd, buff, _DMON_TEMP_BUFFSIZE); - if (len <= 0) { - continue; - } - - while (offset < len) { - struct inotify_event* iev = (struct inotify_event*)&buff[offset]; - - const char *subdir = dmon__find_subdir(watch, iev->wd); - if (subdir) { - char filepath[DMON_MAX_PATH]; - dmon__strcpy(filepath, sizeof(filepath), subdir); - dmon__strcat(filepath, sizeof(filepath), iev->name); - - // TODO: ignore directories if flag is set - - if (stb_sb_count(_dmon.events) == 0) { - usecs_elapsed = 0; - } - dmon__inotify_event dev = { { 0 }, iev->mask, iev->cookie, watch->id, false }; - dmon__strcpy(dev.filepath, sizeof(dev.filepath), filepath); - stb_sb_push(_dmon.events, dev); - } - - offset += sizeof(struct inotify_event) + iev->len; - } - } - } - } - - struct timeval tm; - gettimeofday(&tm, 0); - long dt = (tm.tv_sec - starttm.tv_sec) * 1000000 + tm.tv_usec - starttm.tv_usec; - starttm = tm; - usecs_elapsed += dt; - if (usecs_elapsed > 100000 && stb_sb_count(_dmon.events) > 0) { - dmon__inotify_process_events(); - usecs_elapsed = 0; - } - - pthread_mutex_unlock(&_dmon.mutex); - } - return 0x0; -} - -_DMON_PRIVATE void dmon__mutex_wakeup_lock(void) { - pthread_mutex_lock(&_dmon.wait_flag_mutex); - _dmon.wait_flag = 1; - if (pthread_mutex_trylock(&_dmon.mutex) != 0) { - char send_char = 1; - write(_dmon.wake_event_pipe[1], &send_char, 1); - pthread_mutex_lock(&_dmon.mutex); - } - _dmon.wait_flag = 0; - pthread_mutex_unlock(&_dmon.wait_flag_mutex); -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - close(watch->fd); - stb_sb_free(watch->subdirs); - stb_sb_free(watch->wds); - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - pthread_mutex_init(&_dmon.mutex, NULL); - - _dmon.wait_flag = 0; - int ret_pipe = pipe(_dmon.wake_event_pipe); - DMON_ASSERT(ret_pipe == 0); - int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL); - _DMON_UNUSED(r); - DMON_ASSERT(r == 0 && "pthread_create failed"); - _dmon_init = true; -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - dmon__mutex_wakeup_lock(); - pthread_join(_dmon.thread_handle, NULL); - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - pthread_mutex_unlock(&_dmon.mutex); - pthread_mutex_destroy(&_dmon.mutex); - stb_sb_free(_dmon.events); - _dmon_init = false; -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data, dmon_error *error_code) -{ - DMON_ASSERT(watch_cb); - DMON_ASSERT(rootdir && rootdir[0]); - - dmon__mutex_wakeup_lock(); - - DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - struct stat root_st; - if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || - (root_st.st_mode & S_IRUSR) != S_IRUSR) { - *error_code = DMON_ERROR_OPEN_DIR; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - - - if (S_ISLNK(root_st.st_mode)) { - if (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) { - char linkpath[PATH_MAX]; - char* r = realpath(rootdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); - } else { - *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - } else { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - } - - // add trailing slash - int rootdir_len = (int)strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - watch->fd = inotify_init(); - if (watch->fd < -1) { - *error_code = DMON_ERROR_MONITOR_FAIL; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - - uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - int wd = inotify_add_watch(watch->fd, watch->rootdir, inotify_mask); - if (wd < 0) { - *error_code = DMON_ERROR_WATCH_DIR; - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), ""); // root dir is just a dummy entry - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // recursive mode: enumarate all child directories and add them to watch -#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE - if (flags & DMON_WATCHFLAGS_RECURSIVE) { - dmon__watch_recursive(watch->rootdir, watch->fd, inotify_mask, - (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) ? true : false, watch); - } -#endif - - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - dmon__mutex_wakeup_lock(); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - pthread_mutex_unlock(&_dmon.mutex); -} -#elif DMON_OS_MACOS -// FSEvents MacOS backend -typedef struct dmon__fsevent_event { - char filepath[DMON_MAX_PATH]; - uint64_t event_id; - long event_flags; - dmon_watch_id watch_id; - bool skip; - bool move_valid; -} dmon__fsevent_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - uint32_t watch_flags; - FSEventStreamRef fsev_stream_ref; - dmon__watch_cb* watch_cb; - void* user_data; - char rootdir[DMON_MAX_PATH]; - char rootdir_unmod[DMON_MAX_PATH]; - bool init; -} dmon__watch_state; - -typedef struct dmon__state { - dmon__watch_state watches[DMON_MAX_WATCHES]; - dmon__fsevent_event* events; - int num_watches; - volatile int modify_watches; - pthread_t thread_handle; - dispatch_semaphore_t thread_sem; - pthread_mutex_t mutex; - CFRunLoopRef cf_loop_ref; - CFAllocatorRef cf_alloc_ref; - bool quit; -} dmon__state; - -union dmon__cast_userdata { - void* ptr; - uint32_t id; -}; - -static bool _dmon_init; -static dmon__state _dmon; - -_DMON_PRIVATE void* dmon__cf_malloc(CFIndex size, CFOptionFlags hints, void* info) -{ - _DMON_UNUSED(hints); - _DMON_UNUSED(info); - return DMON_MALLOC(size); -} - -_DMON_PRIVATE void dmon__cf_free(void* ptr, void* info) -{ - _DMON_UNUSED(info); - DMON_FREE(ptr); -} - -_DMON_PRIVATE void* dmon__cf_realloc(void* ptr, CFIndex newsize, CFOptionFlags hints, void* info) -{ - _DMON_UNUSED(hints); - _DMON_UNUSED(info); - return DMON_REALLOC(ptr, (size_t)newsize); -} - -_DMON_PRIVATE void dmon__fsevent_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__fsevent_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - // remove redundant modify events on a single file - if (ev->event_flags & kFSEventStreamEventFlagItemModified) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if ((check_ev->event_flags & kFSEventStreamEventFlagItemModified) && - strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } - } - } else if ((ev->event_flags & kFSEventStreamEventFlagItemRenamed) && !ev->move_valid) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if ((check_ev->event_flags & kFSEventStreamEventFlagItemRenamed) && - check_ev->event_id == (ev->event_id + 1)) { - ev->move_valid = check_ev->move_valid = true; - break; - } - } - - // in some environments like finder file explorer: - // when a file is deleted, it is moved to recycle bin - // so if the destination of the move is not valid, it's probably DELETE or CREATE - // decide CREATE if file exists - if (!ev->move_valid) { - ev->event_flags &= ~kFSEventStreamEventFlagItemRenamed; - - char abs_filepath[DMON_MAX_PATH]; - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id-1]; - dmon__strcpy(abs_filepath, sizeof(abs_filepath), watch->rootdir); - dmon__strcat(abs_filepath, sizeof(abs_filepath), ev->filepath); - - struct stat root_st; - if (stat(abs_filepath, &root_st) != 0) { - ev->event_flags |= kFSEventStreamEventFlagItemRemoved; - } else { - ev->event_flags |= kFSEventStreamEventFlagItemCreated; - } - } - } - } - - // trigger user callbacks - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__fsevent_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - if (ev->event_flags & kFSEventStreamEventFlagItemCreated) { - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } else if (ev->event_flags & kFSEventStreamEventFlagItemModified) { - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } else if (ev->event_flags & kFSEventStreamEventFlagItemRenamed) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if (check_ev->event_flags & kFSEventStreamEventFlagItemRenamed) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir_unmod, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } else if (ev->event_flags & kFSEventStreamEventFlagItemRemoved) { - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } - } - - stb_sb_reset(_dmon.events); -} - -static void* dmon__thread(void* arg) -{ - _DMON_UNUSED(arg); - - struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; - struct timespec rem = { 0, 0 }; - - _dmon.cf_loop_ref = CFRunLoopGetCurrent(); - dispatch_semaphore_signal(_dmon.thread_sem); - - while (!_dmon.quit) { - if (_dmon.modify_watches || pthread_mutex_trylock(&_dmon.mutex) != 0) { - nanosleep(&req, &rem); - continue; - } - - if (_dmon.num_watches == 0) { - nanosleep(&req, &rem); - pthread_mutex_unlock(&_dmon.mutex); - continue; - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - if (!watch->init) { - DMON_ASSERT(watch->fsev_stream_ref); - FSEventStreamScheduleWithRunLoop(watch->fsev_stream_ref, _dmon.cf_loop_ref, - kCFRunLoopDefaultMode); - FSEventStreamStart(watch->fsev_stream_ref); - - watch->init = true; - } - } - - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, kCFRunLoopRunTimedOut); - dmon__fsevent_process_events(); - - pthread_mutex_unlock(&_dmon.mutex); - } - - CFRunLoopStop(_dmon.cf_loop_ref); - _dmon.cf_loop_ref = NULL; - return 0x0; -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - if (watch->fsev_stream_ref) { - FSEventStreamStop(watch->fsev_stream_ref); - FSEventStreamInvalidate(watch->fsev_stream_ref); - FSEventStreamRelease(watch->fsev_stream_ref); - watch->fsev_stream_ref = NULL; - } - - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - pthread_mutex_init(&_dmon.mutex, NULL); - - CFAllocatorContext cf_alloc_ctx = { 0 }; - cf_alloc_ctx.allocate = dmon__cf_malloc; - cf_alloc_ctx.deallocate = dmon__cf_free; - cf_alloc_ctx.reallocate = dmon__cf_realloc; - _dmon.cf_alloc_ref = CFAllocatorCreate(NULL, &cf_alloc_ctx); - - _dmon.thread_sem = dispatch_semaphore_create(0); - DMON_ASSERT(_dmon.thread_sem); - - int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL); - _DMON_UNUSED(r); - DMON_ASSERT(r == 0 && "pthread_create failed"); - - // wait for thread to initialize loop object - dispatch_semaphore_wait(_dmon.thread_sem, DISPATCH_TIME_FOREVER); - - _dmon_init = true; -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - pthread_join(_dmon.thread_handle, NULL); - - dispatch_release(_dmon.thread_sem); - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - pthread_mutex_destroy(&_dmon.mutex); - stb_sb_free(_dmon.events); - if (_dmon.cf_alloc_ref) { - CFRelease(_dmon.cf_alloc_ref); - } - - _dmon_init = false; -} - -_DMON_PRIVATE void dmon__fsevent_callback(ConstFSEventStreamRef stream_ref, void* user_data, - size_t num_events, void* event_paths, - const FSEventStreamEventFlags event_flags[], - const FSEventStreamEventId event_ids[]) -{ - _DMON_UNUSED(stream_ref); - - union dmon__cast_userdata _userdata; - _userdata.ptr = user_data; - dmon_watch_id watch_id = dmon__make_id(_userdata.id); - DMON_ASSERT(watch_id.id > 0); - dmon__watch_state* watch = &_dmon.watches[watch_id.id - 1]; - char abs_filepath[DMON_MAX_PATH]; - char abs_filepath_lower[DMON_MAX_PATH]; - - for (size_t i = 0; i < num_events; i++) { - const char* filepath = ((const char**)event_paths)[i]; - long flags = (long)event_flags[i]; - uint64_t event_id = (uint64_t)event_ids[i]; - dmon__fsevent_event ev; - memset(&ev, 0x0, sizeof(ev)); - - dmon__strcpy(abs_filepath, sizeof(abs_filepath), filepath); - dmon__unixpath(abs_filepath, sizeof(abs_filepath), abs_filepath); - - // normalize path, so it would be the same on both MacOS file-system types (case/nocase) - dmon__tolower(abs_filepath_lower, sizeof(abs_filepath), abs_filepath); - DMON_ASSERT(strstr(abs_filepath_lower, watch->rootdir) == abs_filepath_lower); - - // strip the root dir from the begining - dmon__strcpy(ev.filepath, sizeof(ev.filepath), abs_filepath + strlen(watch->rootdir)); - - ev.event_flags = flags; - ev.event_id = event_id; - ev.watch_id = watch_id; - stb_sb_push(_dmon.events, ev); - } -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data, dmon_error *error_code) -{ - DMON_ASSERT(watch_cb); - DMON_ASSERT(rootdir && rootdir[0]); - - __sync_lock_test_and_set(&_dmon.modify_watches, 1); - pthread_mutex_lock(&_dmon.mutex); - - DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - struct stat root_st; - if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || - (root_st.st_mode & S_IRUSR) != S_IRUSR) { - *error_code = DMON_ERROR_OPEN_DIR; - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); - return dmon__make_id(0); - } - - if (S_ISLNK(root_st.st_mode)) { - if (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) { - char linkpath[PATH_MAX]; - char* r = realpath(rootdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); - } else { - *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); - return dmon__make_id(0); - } - } else { - char rootdir_abspath[DMON_MAX_PATH]; - if (realpath(rootdir, rootdir_abspath) != NULL) { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir_abspath); - } else { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - } - } - - dmon__unixpath(watch->rootdir, sizeof(watch->rootdir), watch->rootdir); - - // add trailing slash - int rootdir_len = (int)strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - dmon__strcpy(watch->rootdir_unmod, sizeof(watch->rootdir_unmod), watch->rootdir); - dmon__tolower(watch->rootdir, sizeof(watch->rootdir), watch->rootdir); - - // create FS objects - CFStringRef cf_dir = CFStringCreateWithCString(NULL, watch->rootdir_unmod, kCFStringEncodingUTF8); - CFArrayRef cf_dirarr = CFArrayCreate(NULL, (const void**)&cf_dir, 1, NULL); - - FSEventStreamContext ctx; - union dmon__cast_userdata userdata; - userdata.id = id; - ctx.version = 0; - ctx.info = userdata.ptr; - ctx.retain = NULL; - ctx.release = NULL; - ctx.copyDescription = NULL; - watch->fsev_stream_ref = FSEventStreamCreate(_dmon.cf_alloc_ref, dmon__fsevent_callback, &ctx, - cf_dirarr, kFSEventStreamEventIdSinceNow, 0.25, - kFSEventStreamCreateFlagFileEvents); - - - CFRelease(cf_dirarr); - CFRelease(cf_dir); - - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - __sync_lock_test_and_set(&_dmon.modify_watches, 1); - pthread_mutex_lock(&_dmon.mutex); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); -} - -#endif - -#endif // DMON_IMPL -#endif // __DMON_H__ diff --git a/lib/dmon/dmon_extra.h b/lib/dmon/dmon_extra.h deleted file mode 100644 index 97631520..00000000 --- a/lib/dmon/dmon_extra.h +++ /dev/null @@ -1,165 +0,0 @@ -#ifndef __DMON_EXTRA_H__ -#define __DMON_EXTRA_H__ - -// -// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved. -// License: https://github.com/septag/dmon#license-bsd-2-clause -// -// Extra header functionality for dmon.h for the backend based on inotify -// -// Add/Remove directory functions: -// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir -// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir -// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take -// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one -// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user -// will be reached. The default maximum is 8192. -// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the -// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched. -// The function dmon_watch_add and dmon_watch_rm are used to this purpose. -// - -#ifndef __DMON_H__ -#error "Include 'dmon.h' before including this file" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir, dmon_error *error_code); -DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir); - -#ifdef __cplusplus -} -#endif - -#ifdef DMON_IMPL -#if DMON_OS_LINUX -DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir, dmon_error *error_code) -{ - DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES); - - bool skip_lock = pthread_self() == _dmon.thread_handle; - - if (!skip_lock) { - dmon__mutex_wakeup_lock(); - } - - dmon__watch_state* watch = &_dmon.watches[id.id - 1]; - - // check if the directory exists - // if watchdir contains absolute/root-included path, try to strip the rootdir from it - // else, we assume that watchdir is correct, so save it as it is - struct stat st; - dmon__watch_subdir subdir; - // FIXME: check if it is a symlink and respect DMON_WATCHFLAGS_FOLLOW_SYMLINKS - // to resolve the link. - if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - } else { - char fullpath[DMON_MAX_PATH]; - dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); - dmon__strcat(fullpath, sizeof(fullpath), watchdir); - if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) { - *error_code = DMON_ERROR_UNSUPPORTED_SYMLINK; - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - } - - int dirlen = (int)strlen(subdir.rootdir); - if (subdir.rootdir[dirlen - 1] != '/') { - subdir.rootdir[dirlen] = '/'; - subdir.rootdir[dirlen + 1] = '\0'; - } - - // check that the directory is not already added - for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) { - if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) { - *error_code = DMON_ERROR_SUBDIR_LOCATION; - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - } - - const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - char fullpath[DMON_MAX_PATH]; - dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); - dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir); - int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask); - if (wd == -1) { - *error_code = DMON_ERROR_WATCH_DIR; - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - - return true; -} - -DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir) -{ - DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES); - - bool skip_lock = pthread_self() == _dmon.thread_handle; - - if (!skip_lock) { - dmon__mutex_wakeup_lock(); - } - - dmon__watch_state* watch = &_dmon.watches[id.id - 1]; - - char subdir[DMON_MAX_PATH]; - dmon__strcpy(subdir, sizeof(subdir), watchdir); - if (strstr(subdir, watch->rootdir) == subdir) { - dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir)); - } - - int dirlen = (int)strlen(subdir); - if (subdir[dirlen - 1] != '/') { - subdir[dirlen] = '/'; - subdir[dirlen + 1] = '\0'; - } - - int i, c = stb_sb_count(watch->subdirs); - for (i = 0; i < c; i++) { - if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) { - break; - } - } - if (i >= c) { - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - inotify_rm_watch(watch->fd, watch->wds[i]); - - /* Remove entry from subdirs and wds by swapping position with the last entry */ - watch->subdirs[i] = stb_sb_last(watch->subdirs); - stb_sb_pop(watch->subdirs); - - watch->wds[i] = stb_sb_last(watch->wds); - stb_sb_pop(watch->wds); - - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return true; -} -#endif // DMON_OS_LINUX -#endif // DMON_IMPL - -#endif // __DMON_EXTRA_H__ - diff --git a/lib/dmon/meson.build b/lib/dmon/meson.build deleted file mode 100644 index 83edd1c9..00000000 --- a/lib/dmon/meson.build +++ /dev/null @@ -1 +0,0 @@ -lite_includes += include_directories('.') diff --git a/licenses/licenses.md b/licenses/licenses.md index 928d88d9..8005c4a7 100644 --- a/licenses/licenses.md +++ b/licenses/licenses.md @@ -22,33 +22,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## septag/dmon - -Copyright 2019 Sepehr Taghdisian. All rights reserved. - -https://github.com/septag/dmon - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ## Fira Sans Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. diff --git a/meson.build b/meson.build index 5b4662c3..b3b03257 100644 --- a/meson.build +++ b/meson.build @@ -69,9 +69,6 @@ endif if not get_option('source-only') libm = cc.find_library('m', required : false) libdl = cc.find_library('dl', required : false) - threads_dep = dependency('threads') - - lua_fallback = ['lua', 'lua_dep'] lua_quick_fallback = [] if get_option('wrap_mode') == 'forcefallback' @@ -97,7 +94,7 @@ if not get_option('source-only') default_options: ['default_library=static'] ) - lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl, threads_dep] + lite_deps = [lua_dep, sdl_dep, freetype_dep, pcre2_dep, libm, libdl] endif #=============================================================================== # Install Configuration @@ -151,7 +148,6 @@ configure_file( ) if not get_option('source-only') - subdir('lib/dmon') subdir('src') subdir('scripts') endif diff --git a/resources/notes-dmon-integration.md b/resources/notes-dmon-integration.md deleted file mode 100644 index 5179df40..00000000 --- a/resources/notes-dmon-integration.md +++ /dev/null @@ -1,54 +0,0 @@ - -`core.set_project_dir`: - Reset project directories and set its directory. - It chdir into the directory, empty the `core.project_directories` and add - the given directory. - `core.add_project_directory`: - Add a new top-level directory to the project. - Also called from modules and commands outside core.init. - local function `scan_project_folder`: - Scan all files for a given top-level project directory. - Can emit a warning about file limit. - Called only from within core.init module. - -`core.scan_project_subdir`: (before was named `core.scan_project_folder`) - scan a single folder, without recursion. Used when too many files. - -Local function `scan_project_folder`: - Populate the project folder top directory. Done only once when the directory - is added to the project. - -`core.add_project_directory`: - Add a new top-level folder to the project. - -`core.set_project_dir`: - Set the initial project directory. - -`core.dir_rescan_add_job`: - Add a job to rescan after an elapsed time a project's subdirectory to fix for any - changes. - -Local function `rescan_project_subdir`: - Rescan a project's subdirectory, compare to the current version and patch the list if - a difference is found. - - -`core.project_scan_thread`: - Should disappear now that we use dmon. - - -`core.project_scan_topdir`: - New function to scan a top level project folder. - - -`config.project_scan_rate`: -`core.project_scan_thread_id`: -`core.reschedule_project_scan`: -`core.project_files_limit`: - A eliminer. - -`core.get_project_files`: - To be fixed. Use `find_project_files_co` for a single directory - -In TreeView remove usage of self.last to detect new scan that changed the files list. - diff --git a/src/api/api.c b/src/api/api.c index 9f74f6b5..1a6e516d 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -1,20 +1,23 @@ #include "api.h" - int luaopen_system(lua_State *L); int luaopen_renderer(lua_State *L); int luaopen_regex(lua_State *L); int luaopen_process(lua_State *L); +int luaopen_dirmonitor(lua_State* L); static const luaL_Reg libs[] = { - { "system", luaopen_system }, - { "renderer", luaopen_renderer }, - { "regex", luaopen_regex }, - { "process", luaopen_process }, + { "system", luaopen_system }, + { "renderer", luaopen_renderer }, + { "regex", luaopen_regex }, + { "process", luaopen_process }, + { "dirmonitor", luaopen_dirmonitor }, { NULL, NULL } }; + void api_load_libs(lua_State *L) { for (int i = 0; libs[i].name; i++) luaL_requiref(L, libs[i].name, libs[i].func, 1); } + diff --git a/src/api/api.h b/src/api/api.h index e7bc57ea..c11fbdb3 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -7,6 +7,7 @@ #define API_TYPE_FONT "Font" #define API_TYPE_PROCESS "Process" +#define API_TYPE_DIRMONITOR "Dirmonitor" #define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key)) diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c new file mode 100644 index 00000000..7f298724 --- /dev/null +++ b/src/api/dirmonitor.c @@ -0,0 +1,203 @@ +#include "api.h" +#include +#ifdef _WIN32 + #include +#elif __linux__ + #include + #include +#else + #include +#endif +#include +#include +#include +#include +#include +#include + +/* +This is *slightly* a clusterfuck. Normally, we'd +have windows wait on a list of handles like inotify, +however, MAXIMUM_WAIT_OBJECTS is 64. Yes, seriously. + +So, for windows, we are recursive. +*/ +struct dirmonitor { + int fd; + #if _WIN32 + HANDLE handle; + char buffer[8192]; + OVERLAPPED overlapped; + bool running; + #endif +}; + +struct dirmonitor* init_dirmonitor() { + struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1); + #ifndef _WIN32 + #if __linux__ + monitor->fd = inotify_init1(IN_NONBLOCK); + #else + monitor->fd = kqueue(); + #endif + #endif + return monitor; +} + +#if _WIN32 +static void close_monitor_handle(struct dirmonitor* monitor) { + if (monitor->handle) { + if (monitor->running) { + BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped); + DWORD error = GetLastError(); + if (result == TRUE || error != ERROR_NOT_FOUND) { + DWORD bytes_transferred; + GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE ); + } + monitor->running = false; + } + CloseHandle(monitor->handle); + } + monitor->handle = NULL; +} +#endif + +void deinit_dirmonitor(struct dirmonitor* monitor) { + #if _WIN32 + close_monitor_handle(monitor); + #else + close(monitor->fd); + #endif + free(monitor); +} + +int check_dirmonitor(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) { + #if _WIN32 + if (!monitor->running) { + if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0) + return GetLastError(); + monitor->running = true; + } + DWORD bytes_transferred; + if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) { + int error = GetLastError(); + return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error; + } + monitor->running = false; + for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) { + change_callback(info->FileNameLength, (char*)info->FileName, data); + if (!info->NextEntryOffset) + break; + } + monitor->running = false; + return 0; + #elif __linux__ + char buf[PATH_MAX + sizeof(struct inotify_event)]; + while (1) { + ssize_t len = read(monitor->fd, buf, sizeof(buf)); + if (len == -1 && errno != EAGAIN) + return errno; + if (len <= 0) + return 0; + for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + ((struct inotify_event*)ptr)->len) + change_callback(((const struct inotify_event *) ptr)->wd, NULL, data); + } + #else + struct kevent event; + while (1) { + struct timespec tm = {0}; + int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm); + if (nev == -1) + return errno; + if (nev <= 0) + return 0; + change_callback(event.ident, NULL, data); + } + #endif +} + +int add_dirmonitor(struct dirmonitor* monitor, const char* path) { + #if _WIN32 + close_monitor_handle(monitor); + monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); + if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE) + return 1; + monitor->handle = NULL; + return -1; + #elif __linux__ + return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); + #else + int fd = open(path, O_RDONLY); + struct kevent change; + EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path); + kevent(monitor->fd, &change, 1, NULL, 0, NULL); + return fd; + #endif +} + +void remove_dirmonitor(struct dirmonitor* monitor, int fd) { + #if _WIN32 + close_monitor_handle(monitor); + #elif __linux__ + inotify_rm_watch(monitor->fd, fd); + #else + close(fd); + #endif +} + +static int f_check_dir_callback(int watch_id, const char* path, void* L) { + #if _WIN32 + char buffer[PATH_MAX*4]; + int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL); + lua_pushlstring(L, buffer, count); + #else + lua_pushnumber(L, watch_id); + #endif + lua_pcall(L, 1, 1, 0); + int result = lua_toboolean(L, -1); + lua_pop(L, 1); + return !result; +} + +static int f_dirmonitor_new(lua_State* L) { + struct dirmonitor** monitor = lua_newuserdata(L, sizeof(struct dirmonitor**)); + *monitor = init_dirmonitor(); + luaL_setmetatable(L, API_TYPE_DIRMONITOR); + return 1; +} + +static int f_dirmonitor_gc(lua_State* L) { + deinit_dirmonitor(*((struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR))); + return 0; +} + +static int f_dirmonitor_watch(lua_State *L) { + lua_pushnumber(L, add_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checkstring(L, 2))); + return 1; +} + +static int f_dirmonitor_unwatch(lua_State *L) { + remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checknumber(L, 2)); + return 0; +} + +static int f_dirmonitor_check(lua_State* L) { + lua_pushnumber(L, check_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), f_check_dir_callback, L)); + return 1; +} +static const luaL_Reg dirmonitor_lib[] = { + { "new", f_dirmonitor_new }, + { "__gc", f_dirmonitor_gc }, + { "watch", f_dirmonitor_watch }, + { "unwatch", f_dirmonitor_unwatch }, + { "check", f_dirmonitor_check }, + {NULL, NULL} +}; + +int luaopen_dirmonitor(lua_State* L) { + luaL_newmetatable(L, API_TYPE_DIRMONITOR); + luaL_setfuncs(L, dirmonitor_lib, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + return 1; +} diff --git a/src/api/system.c b/src/api/system.c index 80c0a2ae..f1f9c3c7 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -7,7 +7,6 @@ #include #include #include "api.h" -#include "../dirmonitor.h" #include "../rencache.h" #ifdef _WIN32 #include @@ -255,26 +254,6 @@ top: lua_pushinteger(L, e.wheel.y); return 2; - case SDL_USEREVENT: - lua_pushstring(L, "dirchange"); - lua_pushinteger(L, e.user.code >> 16); - switch (e.user.code & 0xffff) { - case DMON_ACTION_DELETE: - lua_pushstring(L, "delete"); - break; - case DMON_ACTION_CREATE: - lua_pushstring(L, "create"); - break; - case DMON_ACTION_MODIFY: - lua_pushstring(L, "modify"); - break; - default: - return luaL_error(L, "unknown dmon event action: %d", e.user.code & 0xffff); - } - lua_pushstring(L, e.user.data1); - free(e.user.data1); - return 4; - default: goto top; } @@ -592,29 +571,27 @@ static struct f_type_names fs_names[] = { { 0x0, NULL }, }; -static int f_get_fs_type(lua_State *L) { - const char *path = luaL_checkstring(L, 1); - struct statfs buf; - int status = statfs(path, &buf); - if (status != 0) { - return luaL_error(L, "error calling statfs on %s", path); - } - for (int i = 0; fs_names[i].magic; i++) { - if (fs_names[i].magic == buf.f_type) { - lua_pushstring(L, fs_names[i].name); - return 1; - } - } - lua_pushstring(L, "unknown"); - return 1; -} -#else -static int f_return_unknown(lua_State *L) { - lua_pushstring(L, "unknown"); - return 1; -} #endif +static int f_get_fs_type(lua_State *L) { + #if __linux__ + const char *path = luaL_checkstring(L, 1); + struct statfs buf; + int status = statfs(path, &buf); + if (status != 0) { + return luaL_error(L, "error calling statfs on %s", path); + } + for (int i = 0; fs_names[i].magic; i++) { + if (fs_names[i].magic == buf.f_type) { + lua_pushstring(L, fs_names[i].name); + return 1; + } + } + #endif + lua_pushstring(L, "unknown"); + return 1; +} + static int f_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -815,66 +792,6 @@ static int f_load_native_plugin(lua_State *L) { return result; } -static int f_watch_dir(lua_State *L) { - const char *path = luaL_checkstring(L, 1); - /* On linux we watch non-recursively and we add/remove each sub-directory explicitly - * using the function system.watch_dir_add/rm. On other systems we watch recursively - * and system.watch_dir_add/rm are dummy functions that always returns true. */ -#if __linux__ - const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS; -#elif __APPLE__ - const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS | DMON_WATCHFLAGS_RECURSIVE; -#else - const uint32_t dmon_flags = DMON_WATCHFLAGS_RECURSIVE; -#endif - dmon_error error; - dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL, &error); - if (watch_id.id == 0) { - lua_pushnil(L); - lua_pushstring(L, dmon_error_str(error)); - return 2; - } - lua_pushinteger(L, watch_id.id); - return 1; -} - -static int f_unwatch_dir(lua_State *L) { - dmon_watch_id watch_id; - watch_id.id = luaL_checkinteger(L, 1); - dmon_unwatch(watch_id); - return 0; -} - -#if __linux__ -static int f_watch_dir_add(lua_State *L) { - dmon_watch_id watch_id; - watch_id.id = luaL_checkinteger(L, 1); - const char *subdir = luaL_checkstring(L, 2); - dmon_error error_code; - int success = dmon_watch_add(watch_id, subdir, &error_code); - if (!success) { - lua_pushboolean(L, 0); - lua_pushstring(L, dmon_error_str(error_code)); - return 2; - } - lua_pushboolean(L, 1); - return 1; -} - -static int f_watch_dir_rm(lua_State *L) { - dmon_watch_id watch_id; - watch_id.id = luaL_checkinteger(L, 1); - const char *subdir = luaL_checkstring(L, 2); - lua_pushboolean(L, dmon_watch_rm(watch_id, subdir)); - return 1; -} -#else -static int f_return_true(lua_State *L) { - lua_pushboolean(L, 1); - return 1; -} -#endif - #ifdef _WIN32 #define PATHSEP '\\' #else @@ -961,18 +878,8 @@ static const luaL_Reg lib[] = { { "fuzzy_match", f_fuzzy_match }, { "set_window_opacity", f_set_window_opacity }, { "load_native_plugin", f_load_native_plugin }, - { "watch_dir", f_watch_dir }, - { "unwatch_dir", f_unwatch_dir }, { "path_compare", f_path_compare }, -#if __linux__ - { "watch_dir_add", f_watch_dir_add }, - { "watch_dir_rm", f_watch_dir_rm }, { "get_fs_type", f_get_fs_type }, -#else - { "watch_dir_add", f_return_true }, - { "watch_dir_rm", f_return_true }, - { "get_fs_type", f_return_unknown }, -#endif { NULL, NULL } }; diff --git a/src/dirmonitor.c b/src/dirmonitor.c deleted file mode 100644 index 0063e400..00000000 --- a/src/dirmonitor.c +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include - -#include - -#define DMON_IMPL -#include "dmon.h" -#include "dmon_extra.h" - -#include "dirmonitor.h" - -static void send_sdl_event(dmon_watch_id watch_id, dmon_action action, const char *filepath) { - SDL_Event ev; - const int size = strlen(filepath) + 1; - /* The string allocated below should be deallocated as soon as the event is - treated in the SDL main loop. */ - char *new_filepath = malloc(size); - if (!new_filepath) return; - memcpy(new_filepath, filepath, size); -#ifdef _WIN32 - for (int i = 0; i < size; i++) { - if (new_filepath[i] == '/') { - new_filepath[i] = '\\'; - } - } -#endif - SDL_zero(ev); - ev.type = SDL_USEREVENT; - ev.user.code = ((watch_id.id & 0xffff) << 16) | (action & 0xffff); - ev.user.data1 = new_filepath; - SDL_PushEvent(&ev); -} - -void dirmonitor_init() { - dmon_init(); - /* In theory we should register our user event but since we - have just one type of user event this is not really needed. */ - /* sdl_dmon_event_type = SDL_RegisterEvents(1); */ -} - -void dirmonitor_deinit() { - dmon_deinit(); -} - -void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir, - const char *filepath, const char *oldfilepath, void *user) -{ - (void) rootdir; - (void) user; - switch (action) { - case DMON_ACTION_MOVE: - send_sdl_event(watch_id, DMON_ACTION_DELETE, oldfilepath); - send_sdl_event(watch_id, DMON_ACTION_CREATE, filepath); - break; - default: - send_sdl_event(watch_id, action, filepath); - } -} - diff --git a/src/dirmonitor.h b/src/dirmonitor.h deleted file mode 100644 index 074a9ae8..00000000 --- a/src/dirmonitor.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef DIRMONITOR_H -#define DIRMONITOR_H - -#include - -#include "dmon.h" -#include "dmon_extra.h" - -void dirmonitor_init(); -void dirmonitor_deinit(); -void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir, - const char *filepath, const char *oldfilepath, void *user); - -#endif - diff --git a/src/main.c b/src/main.c index 9fcbeab6..94d2674b 100644 --- a/src/main.c +++ b/src/main.c @@ -7,15 +7,13 @@ #ifdef _WIN32 #include -#elif __linux__ +#elif __linux__ || __FreeBSD__ #include #include #elif __APPLE__ #include #endif -#include "dirmonitor.h" - SDL_Window *window; @@ -108,8 +106,6 @@ int main(int argc, char **argv) { SDL_DisplayMode dm; SDL_GetCurrentDisplayMode(0, &dm); - dirmonitor_init(); - window = SDL_CreateWindow( "", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); @@ -192,7 +188,6 @@ init_lua: lua_close(L); ren_free_window_resources(); - dirmonitor_deinit(); return EXIT_SUCCESS; } diff --git a/src/meson.build b/src/meson.build index 1eaf87fd..8a4f1272 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,10 +1,10 @@ lite_sources = [ 'api/api.c', + 'api/dirmonitor.c', 'api/renderer.c', 'api/regex.c', 'api/system.c', 'api/process.c', - 'dirmonitor.c', 'renderer.c', 'renwindow.c', 'rencache.c',