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) function core.project_subdir_set_show(dir, filename, show)
dir.shown_subdir[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 fullpath = dir.name .. PATHSEP .. filename
local watch_fn = show and system.watch_dir_add or system.watch_dir_rm local success = (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath)
local success = watch_fn(dir.watch_id, fullpath)
if not success then if not success then
core.log("Internal warning: error calling system.watch_dir_%s", show and "add" or "rm") core.log("Internal warning: error calling system.watch_dir_%s", show and "add" or "rm")
end end
@ -228,9 +227,14 @@ end
local function project_scan_add_entry(dir, fileinfo) 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) local index, match = file_search(dir.files, fileinfo)
if not match then if not match then
table.insert(dir.files, index, fileinfo) 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 dir.is_dirty = true
end end
end end
@ -252,7 +256,7 @@ local function files_list_match(a, i1, n, b)
end end
-- arguments like for files_list_match -- 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 m = #bs
local i, j = 1, 1 local i, j = 1, 1
while i <= m or i <= n do while i <= m or i <= n do
@ -262,6 +266,7 @@ local function files_list_replace(as, i1, n, bs)
then then
table.insert(as, i1 + i, b) table.insert(as, i1 + i, b)
i, j, n = i + 1, j + 1, n + 1 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 elseif j > m or system.path_compare(a.filename, a.type, b.filename, b.type) then
table.remove(as, i1 + i) table.remove(as, i1 + i)
n = n - 1 n = n - 1
@ -297,7 +302,19 @@ local function rescan_project_subdir(dir, filename_rooted)
end end
if not files_list_match(dir.files, index, n, new_files) then 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 dir.is_dirty = true
return true return true
end end
@ -316,35 +333,54 @@ local function add_dir_scan_thread(dir)
end) end)
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. -- Populate a project folder top directory by scanning the filesystem.
local function scan_project_folder(index) local function scan_project_folder(index)
local dir = core.project_directories[index] local dir = core.project_directories[index]
if PLATFORM == "Linux" then local fstype = system.get_fs_type(dir.name)
local fstype = system.get_fs_type(dir.name) dir.force_rescan = (fstype == "nfs" or fstype == "fuse")
dir.force_rescan = (fstype == "nfs" or fstype == "fuse") if not dir.force_rescan then
dir.watch_id = system.watch_dir(dir.name)
end end
local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 0, timed_max_files_pred) 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 if not complete then
dir.slow_filesystem = not complete and (entries_count <= config.max_project_files) dir.slow_filesystem = not complete and (entries_count <= config.max_project_files)
dir.files_limit = true 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. if core.status_view then -- May be not yet initialized.
show_max_files_warning(dir) show_max_files_warning(dir)
end end
else
if not dir.force_rescan then
dir.watch_id = system.watch_dir(dir.name, true)
end
end end
dir.files = t dir.files = t
if dir.force_rescan then if dir.force_rescan then
add_dir_scan_thread(dir) add_dir_scan_thread(dir)
else else
if not dir.files_limit then
folder_add_subdirs_watch(dir)
end
core.dir_rescan_add_job(dir, ".") core.dir_rescan_add_job(dir, ".")
end end
end end
@ -385,15 +421,16 @@ local function rescan_project_directories()
core.project_directories = {} core.project_directories = {}
for i = 1, n do -- add again the directories in the project for i = 1, n do -- add again the directories in the project
local dir = core.add_project_directory(save_project_dirs[i].name) local dir = core.add_project_directory(save_project_dirs[i].name)
-- The shown_subdir is only used on linux for very large directories. if dir.files_limit then
-- replay them on the newly scanned project. for subdir, show in pairs(save_project_dirs[i].shown_subdir) do
for subdir, show in pairs(save_project_dirs[i].shown_subdir) do for j = 1, #dir.files do
for j = 1, #dir.files do if dir.files[j].filename == subdir then
if dir.files[j].filename == subdir then -- The instructions below match when happens in TreeView:on_mouse_pressed.
-- the instructions above 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.update_project_subdir(dir, subdir, show)
core.project_subdir_set_show(dir, subdir, show) core.project_subdir_set_show(dir, subdir, show)
break break
end
end end
end end
end end
@ -411,9 +448,15 @@ end
function core.update_project_subdir(dir, filename, expanded) 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) local index, n, file = project_subdir_bounds(dir, filename)
if index then if index then
local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {} 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) files_list_replace(dir.files, index, n, new_files)
dir.is_dirty = true dir.is_dirty = true
return true return true
@ -510,6 +553,9 @@ local function project_scan_remove_file(dir, filepath)
local index, match = file_search(dir.files, fileinfo) local index, match = file_search(dir.files, fileinfo)
if match then if match then
table.remove(dir.files, index) table.remove(dir.files, index)
if dir.files_limit and filetype == "dir" then
dir.shown_subdir[filepath] = nil
end
dir.is_dirty = true dir.is_dirty = true
return return
end end

View File

@ -584,6 +584,11 @@ static int f_get_fs_type(lua_State *L) {
lua_pushstring(L, "unknown"); lua_pushstring(L, "unknown");
return 1; return 1;
} }
#else
static int f_return_unknown(lua_State *L) {
lua_pushstring(L, "unknown");
return 1;
}
#endif #endif
@ -715,8 +720,14 @@ static int f_set_window_opacity(lua_State *L) {
static int f_watch_dir(lua_State *L) { static int f_watch_dir(lua_State *L) {
const char *path = luaL_checkstring(L, 1); const char *path = luaL_checkstring(L, 1);
const int recursive = lua_toboolean(L, 2); /* On linux we watch non-recursively and we add/remove each sub-directory explicitly
uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0); * 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); 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"); } if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); }
lua_pushnumber(L, watch_id.id); 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)); lua_pushboolean(L, dmon_watch_rm(watch_id, subdir));
return 1; return 1;
} }
#else
static int f_return_true(lua_State *L) {
lua_pushboolean(L, 1);
return 1;
}
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
@ -839,6 +855,10 @@ static const luaL_Reg lib[] = {
{ "watch_dir_add", f_watch_dir_add }, { "watch_dir_add", f_watch_dir_add },
{ "watch_dir_rm", f_watch_dir_rm }, { "watch_dir_rm", f_watch_dir_rm },
{ "get_fs_type", f_get_fs_type }, { "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 #endif
{ NULL, NULL } { NULL, NULL }
}; };