Always watch/unwatch subdirectories on all systems

Simplifies and uniformize the logic on the Lua side for the
setting of directories' watches. Now we always use the methods:

systems.watch_dir_add / rm

on all the project's directories at any depth when we are not
in files limit mode.

In files limited mode the functions systems.watch_dir_add/rm are
called only on the expanded folders. The shown_subdir table is also
updated only in files limited mode.

On the C side, using the dmon library, we remove the recursive argument
from the system.watch_dir and we always call it recursively except on
Linux. At the same time the functions:

systems.watch_dir_add / rm

are provided but as dummy functions that does nothing except on Linux
where they work as before to add / remove sub-directories in the inotify
watch.

In this was on the Lua side we always act we if the watches needed to be
set for each sub-directory explicitly, independently of the system.

The important improvement introduced is that we always avoid calling
dmon_watch recursively on Linux. This latter thing is problematic with
inotify and is therefore avoided on Linux.

On the other side we simplifies the logic on the Lua side and remove
conditions based on the OS used.
This commit is contained in:
Francesco Abbate 2021-12-29 08:19:44 +01:00
parent 88ed312f6b
commit adaf023541
2 changed files with 95 additions and 29 deletions

View File

@ -180,10 +180,9 @@ end
function core.project_subdir_set_show(dir, filename, show)
dir.shown_subdir[filename] = show
if dir.files_limit and PLATFORM == "Linux" then
if dir.files_limit and not dir.force_rescan then
local fullpath = dir.name .. PATHSEP .. filename
local watch_fn = show and system.watch_dir_add or system.watch_dir_rm
local success = watch_fn(dir.watch_id, fullpath)
local success = (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath)
if not success then
core.log("Internal warning: error calling system.watch_dir_%s", show and "add" or "rm")
end
@ -228,9 +227,14 @@ end
local function project_scan_add_entry(dir, fileinfo)
assert(not dir.force_rescan, "should be used on when force_rescan is unset")
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)
end
dir.is_dirty = true
end
end
@ -252,7 +256,7 @@ local function files_list_match(a, i1, n, b)
end
-- arguments like for files_list_match
local function files_list_replace(as, i1, n, bs)
local function files_list_replace(as, i1, n, bs, insert_hook)
local m = #bs
local i, j = 1, 1
while i <= m or i <= n do
@ -262,6 +266,7 @@ local function files_list_replace(as, i1, n, bs)
then
table.insert(as, i1 + i, b)
i, j, n = i + 1, j + 1, n + 1
if insert_hook then insert_hook(b) end
elseif j > m or system.path_compare(a.filename, a.type, b.filename, b.type) then
table.remove(as, i1 + i)
n = n - 1
@ -297,7 +302,19 @@ local function rescan_project_subdir(dir, filename_rooted)
end
if not files_list_match(dir.files, index, n, new_files) then
files_list_replace(dir.files, index, n, new_files)
-- 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 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
@ -316,35 +333,54 @@ local function add_dir_scan_thread(dir)
end)
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]
if PLATFORM == "Linux" then
local fstype = system.get_fs_type(dir.name)
dir.force_rescan = (fstype == "nfs" or fstype == "fuse")
local fstype = system.get_fs_type(dir.name)
dir.force_rescan = (fstype == "nfs" or fstype == "fuse")
if not dir.force_rescan then
dir.watch_id = system.watch_dir(dir.name)
end
local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 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 not dir.force_rescan then
-- Watch non-recursively on Linux only.
-- The reason is recursively watching with dmon on linux
-- doesn't work on very large directories.
dir.watch_id = system.watch_dir(dir.name, PLATFORM ~= "Linux")
end
if core.status_view then -- May be not yet initialized.
show_max_files_warning(dir)
end
else
if not dir.force_rescan then
dir.watch_id = system.watch_dir(dir.name, true)
end
end
dir.files = t
if dir.force_rescan then
add_dir_scan_thread(dir)
else
if not dir.files_limit then
folder_add_subdirs_watch(dir)
end
core.dir_rescan_add_job(dir, ".")
end
end
@ -385,15 +421,16 @@ local function rescan_project_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)
-- The shown_subdir is only used on linux for very large directories.
-- replay them on the newly scanned project.
for subdir, show in pairs(save_project_dirs[i].shown_subdir) do
for j = 1, #dir.files do
if dir.files[j].filename == subdir then
-- the instructions above match when happens in TreeView:on_mouse_pressed
core.update_project_subdir(dir, subdir, show)
core.project_subdir_set_show(dir, subdir, show)
break
if dir.files_limit then
for subdir, show in pairs(save_project_dirs[i].shown_subdir) do
for j = 1, #dir.files do
if dir.files[j].filename == subdir then
-- The instructions below match when happens in TreeView:on_mouse_pressed.
-- We perform the operations only once iff the subdir is in dir.files.
core.update_project_subdir(dir, subdir, show)
core.project_subdir_set_show(dir, subdir, show)
break
end
end
end
end
@ -411,9 +448,15 @@ 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")
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, {}, 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.
files_list_replace(dir.files, index, n, new_files)
dir.is_dirty = true
return true
@ -510,6 +553,9 @@ local function project_scan_remove_file(dir, filepath)
local index, match = file_search(dir.files, fileinfo)
if match then
table.remove(dir.files, index)
if dir.files_limit and filetype == "dir" then
dir.shown_subdir[filepath] = nil
end
dir.is_dirty = true
return
end

View File

@ -584,6 +584,11 @@ static int f_get_fs_type(lua_State *L) {
lua_pushstring(L, "unknown");
return 1;
}
#else
static int f_return_unknown(lua_State *L) {
lua_pushstring(L, "unknown");
return 1;
}
#endif
@ -715,8 +720,14 @@ static int f_set_window_opacity(lua_State *L) {
static int f_watch_dir(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
const int recursive = lua_toboolean(L, 2);
uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0);
/* 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 = 0;
#else
const uint32_t dmon_flags = DMON_WATCHFLAGS_RECURSIVE;
#endif
dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL);
if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); }
lua_pushnumber(L, watch_id.id);
@ -746,6 +757,11 @@ static int f_watch_dir_rm(lua_State *L) {
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
@ -839,6 +855,10 @@ static const luaL_Reg lib[] = {
{ "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 }
};