First integration of dmon for directory monitoring

This commit is contained in:
Francesco Abbate 2021-07-12 18:21:27 +02:00
parent 18189e63b6
commit 1ba385eb5e
9 changed files with 1773 additions and 6 deletions

View File

@ -99,6 +99,18 @@ local function compare_file(a, b)
return a.filename < b.filename return a.filename < b.filename
end 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, size_limit)
local info = system.get_file_info(root .. file)
if info then
info.filename = strip_leading_path(file)
end
return info and info.size < size_limit and info
end
-- "root" will by an absolute path without trailing '/' -- "root" will by an absolute path without trailing '/'
-- "path" will be a path starting with '/' and without trailing '/' -- "path" will be a path starting with '/' and without trailing '/'
-- or the empty string. -- or the empty string.
@ -117,10 +129,8 @@ local function get_directory_files(root, path, t, recursive, begin_hook)
local max_entries = config.max_project_files local max_entries = config.max_project_files
for _, file in ipairs(all) do for _, file in ipairs(all) do
if not common.match_pattern(file, config.ignore_files) then if not common.match_pattern(file, config.ignore_files) then
local file = path .. PATHSEP .. file local info = get_project_file_info(root, path .. PATHSEP .. file, size_limit)
local info = system.get_file_info(root .. file) if info then
if info and info.size < size_limit then
info.filename = strip_leading_path(file)
table.insert(info.type == "dir" and dirs or files, info) table.insert(info.type == "dir" and dirs or files, info)
entries_count = entries_count + 1 entries_count = entries_count + 1
if recursive and entries_count > max_entries then return nil, entries_count end if recursive and entries_count > max_entries then return nil, entries_count end
@ -276,6 +286,69 @@ function core.project_files_number()
end end
local function file_search(files, info)
local filename, type = info.filename, info.type
local inf, sup = 1, #files
while sup - inf > 8 do
local curr = math.floor((inf + sup) / 2)
if system.path_compare(filename, type, files[curr].filename, files[curr].type) then
sup = curr - 1
else
inf = curr
end
end
repeat
if files[inf].filename == filename then
return inf, true
end
inf = inf + 1
until inf > sup or system.path_compare(filename, type, files[inf].filename, files[inf].type)
return inf, false
end
local function project_scan_remove_file(watch_id, filepath)
local project_dir_entry
for i = 1, #core.project_directories do
if core.project_directories[i].watch_id == watch_id then
project_dir_entry = core.project_directories[i]
end
end
if not project_dir_entry then return end
print("LOOKING for", filepath, " in", project_dir_entry and project_dir_entry.name)
local fileinfo = { filename = filepath }
for _, filetype in ipairs {"dir", "file"} do
fileinfo.type = filetype
local index, match = file_search(project_dir_entry.files, fileinfo)
if match then
print("FOUND", filepath, " at index", index)
table.remove(project_dir_entry.files, index)
project_dir_entry.is_dirty = true
return
end
end
end
local function project_scan_add_file(watch_id, filepath)
local project_dir_entry
for i = 1, #core.project_directories do
if core.project_directories[i].watch_id == watch_id then
project_dir_entry = core.project_directories[i]
end
end
if not project_dir_entry then return end
local size_limit = config.file_size_limit * 10e5
local fileinfo = get_project_file_info(project_dir_entry.name, PATHSEP .. filepath, size_limit)
local index, match = file_search(project_dir_entry.files, fileinfo)
if not match then
table.insert(project_dir_entry.files, index, fileinfo)
project_dir_entry.is_dirty = true
return
end
end
-- create a directory using mkdir but may need to create the parent -- create a directory using mkdir but may need to create the parent
-- directories as well. -- directories as well.
local function create_user_directory() local function create_user_directory()
@ -373,10 +446,13 @@ function core.add_project_directory(path)
-- will be simply the name of the directory, without its path. -- will be simply the name of the directory, without its path.
-- The field item.topdir will identify it as a top level directory. -- The field item.topdir will identify it as a top level directory.
path = common.normalize_path(path) path = common.normalize_path(path)
local watch_id = system.watch_dir(path);
table.insert(core.project_directories, { table.insert(core.project_directories, {
name = path, name = path,
item = {filename = common.basename(path), type = "dir", topdir = true}, item = {filename = common.basename(path), type = "dir", topdir = true},
files = {} files = {},
is_dirty = true,
watch_id = watch_id,
}) })
end end
@ -916,6 +992,15 @@ function core.try(fn, ...)
end end
function core.on_dir_change(watch_id, action, filepath)
if action == "delete" then
project_scan_remove_file(watch_id, filepath)
elseif action == "create" then
project_scan_add_file(watch_id, filepath)
end
end
function core.on_event(type, ...) function core.on_event(type, ...)
local did_keymap = false local did_keymap = false
if type == "textinput" then if type == "textinput" then
@ -951,6 +1036,9 @@ function core.on_event(type, ...)
end end
elseif type == "focuslost" then elseif type == "focuslost" then
core.root_view:on_focus_lost(...) core.root_view:on_focus_lost(...)
elseif type == "dirchange" then
print("DEBUG: dirchange", select(1, ...), select(2, ...), select(3, ...))
core.on_dir_change(...)
elseif type == "quit" then elseif type == "quit" then
core.quit() core.quit()
end end

View File

@ -111,11 +111,12 @@ function TreeView:check_cache()
if not last_files then if not last_files then
self.last[dir.name] = dir.files self.last[dir.name] = dir.files
else else
if dir.files ~= last_files then if dir.is_dirty or dir.files ~= last_files then
self:invalidate_cache(dir.name) self:invalidate_cache(dir.name)
self.last[dir.name] = dir.files self.last[dir.name] = dir.files
end end
end end
dir.is_dirty = false
end end
end end

View File

@ -22,6 +22,33 @@ 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. 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 ## Fira Sans
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.

View File

@ -6,6 +6,7 @@
#include <errno.h> #include <errno.h>
#include <sys/stat.h> #include <sys/stat.h>
#include "api.h" #include "api.h"
#include "dirmonitor.h"
#include "rencache.h" #include "rencache.h"
#ifdef _WIN32 #ifdef _WIN32
#include <direct.h> #include <direct.h>
@ -222,6 +223,14 @@ top:
lua_pushnumber(L, e.wheel.y); lua_pushnumber(L, e.wheel.y);
return 2; return 2;
case SDL_USEREVENT:
lua_pushstring(L, "dirchange");
lua_pushnumber(L, e.user.code >> 16);
lua_pushstring(L, (e.user.code & 0xffff) == DMON_ACTION_DELETE ? "delete" : "create");
lua_pushstring(L, e.user.data1);
free(e.user.data1);
return 4;
default: default:
goto top; goto top;
} }
@ -637,6 +646,56 @@ static int f_set_window_opacity(lua_State *L) {
return 1; return 1;
} }
static void watch_callback(dmon_watch_id watch_id, dmon_action action, const char* rootdir,
const char* filepath, const char* oldfilepath, void* user)
{
(void)(user);
(void)(rootdir);
dirmonitor_push_event(watch_id, action, filepath, oldfilepath);
}
static int f_watch_dir(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
fprintf(stderr, "DEBUG: watching dir: %s\n", path); fflush(stderr);
dmon_watch_id watch_id = dmon_watch(path, watch_callback, DMON_WATCHFLAGS_RECURSIVE, NULL);
lua_pushnumber(L, watch_id.id);
// FIXME: we ignore if there is an error.
return 1;
}
#ifdef _WIN32
#define PATHSEP '\\'
#else
#define PATHSEP '/'
#endif
static int f_path_compare(lua_State *L) {
const char *path1 = luaL_checkstring(L, 1);
const char *type1_s = luaL_checkstring(L, 2);
const char *path2 = luaL_checkstring(L, 3);
const char *type2_s = luaL_checkstring(L, 4);
const int len1 = strlen(path1), len2 = strlen(path2);
int type1 = strcmp(type1_s, "dir") != 0;
int type2 = strcmp(type2_s, "dir") != 0;
int i;
for (i = 0; i < len1 && i < len2; i++) {
if (path1[i] != path2[i]) break;
}
if (strchr(path1 + i, PATHSEP)) {
type1 = 0;
}
if (strchr(path2 + i, PATHSEP)) {
type2 = 0;
}
if (type1 != type2) {
lua_pushboolean(L, type1 < type2);
return 1;
}
lua_pushboolean(L, strcmp(path1 + i, path2 + i) < 0);
return 1;
}
static const luaL_Reg lib[] = { static const luaL_Reg lib[] = {
{ "poll_event", f_poll_event }, { "poll_event", f_poll_event },
@ -664,6 +723,8 @@ static const luaL_Reg lib[] = {
{ "exec", f_exec }, { "exec", f_exec },
{ "fuzzy_match", f_fuzzy_match }, { "fuzzy_match", f_fuzzy_match },
{ "set_window_opacity", f_set_window_opacity }, { "set_window_opacity", f_set_window_opacity },
{ "watch_dir", f_watch_dir },
{ "path_compare", f_path_compare },
{ NULL, NULL } { NULL, NULL }
}; };

56
src/dirmonitor.c Normal file
View File

@ -0,0 +1,56 @@
#include <stdio.h>
#include <string.h>
#include <SDL.h>
#define DMON_IMPL
#include "dmon.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;
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;
fprintf(stderr, "DEBUG: send watch_id: %d action; %d\n", watch_id.id, action); fflush(stderr);
ev.user.code = ((watch_id.id & 0xffff) << 16) | (action & 0xffff);
ev.user.data1 = new_filepath;
SDL_PushEvent(&ev);
}
void dirmonitor_init() {
dmon_init();
/* FIXME: not needed ? */
/* sdl_dmon_event_type = SDL_RegisterEvents(1); */
}
void dirmonitor_deinit() {
dmon_deinit();
}
void dirmonitor_push_event(dmon_watch_id watch_id, dmon_action action, const char *filepath,
const char *oldfilepath)
{
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;
case DMON_ACTION_MODIFY:
break;
default:
send_sdl_event(watch_id, action, filepath);
}
}

14
src/dirmonitor.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef DIRMONITOR_H
#define DIRMONITOR_H
#include <stdint.h>
#include "dmon.h"
void dirmonitor_init();
void dirmonitor_deinit();
void dirmonitor_push_event(dmon_watch_id watch_id, dmon_action action, const char *filepath,
const char *oldfilepath);
#endif

1514
src/dmon.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,8 @@
#include <mach-o/dyld.h> #include <mach-o/dyld.h>
#endif #endif
#include "dirmonitor.h"
SDL_Window *window; SDL_Window *window;
@ -108,6 +110,8 @@ int main(int argc, char **argv) {
SDL_DisplayMode dm; SDL_DisplayMode dm;
SDL_GetCurrentDisplayMode(0, &dm); SDL_GetCurrentDisplayMode(0, &dm);
dirmonitor_init();
window = SDL_CreateWindow( window = SDL_CreateWindow(
"", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8, "", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
@ -191,6 +195,7 @@ init_lua:
lua_close(L); lua_close(L);
ren_free_window_resources(); ren_free_window_resources();
dirmonitor_deinit();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -6,6 +6,7 @@ lite_sources = [
'api/regex.c', 'api/regex.c',
'api/system.c', 'api/system.c',
'api/process.c', 'api/process.c',
'dirmonitor.c',
'renderer.c', 'renderer.c',
'renwindow.c', 'renwindow.c',
'fontdesc.c', 'fontdesc.c',